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.
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/rtti/validate/module.f.d.mts +81 -19
  274. package/fjs/types/rtti/validate/module.f.mjs +107 -61
  275. package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
  276. package/fjs/types/rtti/validate/proof.f.mjs +186 -197
  277. package/fjs/types/sorted_set/module.f.d.mts +18 -0
  278. package/fjs/types/sorted_set/module.f.mjs +22 -0
  279. package/fjs/types/sorted_set/proof.f.d.mts +1 -0
  280. package/fjs/types/sorted_set/proof.f.mjs +16 -1
  281. package/fjs/types/uint8array/module.f.d.mts +1 -1
  282. package/fjs/types/uint8array/module.f.mjs +1 -1
  283. package/fjs/website/module.f.d.mts +3 -3
  284. package/fjs/website/module.f.mjs +4 -7
  285. package/fjs/website/proof.f.mjs +2 -1
  286. package/package.json +2 -2
  287. package/fjs/dev/package_json/module.f.d.mts +0 -39
  288. package/fjs/dev/package_json/module.f.mjs +0 -40
  289. package/fjs/dev/package_json/proof.f.d.mts +0 -6
  290. package/fjs/dev/package_json/proof.f.mjs +0 -32
  291. package/fjs/effects/eff/module.f.d.mts +0 -20
  292. package/fjs/effects/eff/module.f.mjs +0 -70
  293. package/fjs/effects/eff/proof.f.d.mts +0 -15
  294. package/fjs/effects/eff/proof.f.mjs +0 -69
  295. package/fjs/effects/eff/types.d.ts +0 -71
  296. package/fjs/types/rtti/validate/types.d.ts +0 -6
@@ -1,21 +1,27 @@
1
1
  /**
2
2
  * JavaScript tokenizer built as a range-map state machine over code points,
3
3
  * producing tokens for keywords, identifiers, punctuators, comments, strings,
4
- * and numeric literals (including `BigFloat`).
4
+ * and numeric literals.
5
+ *
6
+ * Numeric scanning is lexeme-first: a `number` token carries the exact source
7
+ * text and no derived numeric value, so tokenization stays bounded by the
8
+ * input and never fails because a coefficient or exponent is too large for a
9
+ * runtime numeric type.
5
10
  *
6
11
  * @module
7
12
  *
8
- * @import { Scan, StateScan } from '../../types/function/operator/types.ts'
9
- * @import { RangeMapArray, RangeMerge } from '../../types/range_map/types.ts'
13
+ * @import { Reduce, Scan, StateScan } from '../../types/function/operator/types.ts'
14
+ * @import { RangeMerge } from '../../types/range_map/types.ts'
10
15
  * @import { List } from '../../types/list/types.ts'
11
16
  * @import { Entry } from '../../types/ordered_map/types.ts'
12
17
  * @import { Range as NumberRange } from '../../types/range/types.ts'
13
- * @import { StringToken, NumberToken, BigIntToken, ErrorToken, WhitespaceToken, NewLineToken, IdToken, CommentToken, EofToken, JsToken, TokenMetadata, JsTokenWithMetadata, _TokenizerStateWithMetadata, _TokenizerState, _ErrorMessage, _InitialState, _ParseIdState, _ParseWhitespaceState, _ParseNewLineState, _ParseStringState, _ParseEscapeCharState, _ParseOperatorState, _ParseCommentState, _ParseUnicodeCharState, _ParseNumberState, _ParseNumberBuffer, _InvalidNumberState, _EofState, _CharCodeOrEof, _ToToken, _CreateToToken, _RangeFunc, _RangeMapToToken, } from './types.ts'
18
+ * @import { JsToken, TokenMetadata, JsTokenWithMetadata, _TokenizerStateWithMetadata, _TokenizerState, _ErrorMessage, _InitialState, _ParseIdState, _ParseWhitespaceState, _ParseNewLineState, _ParseStringState, _ParseEscapeCharState, _ParseOperatorState, _ParseCommentState, _ParseUnicodeCharState, _ParseNumberState, _InvalidNumberState, _EofState, _CharCodeOrEof, _ToToken, _CreateToToken, _RangeFunc, _RangeMapToToken, TriviaKind, } from './types.ts'
14
19
  */
15
20
 
16
21
  import { strictEqual } from '../../types/function/operator/module.f.mjs'
17
22
  import { merge, fromRange, get } from '../../types/range_map/module.f.mjs'
18
23
  import { empty, stateScan, flat, toArray, reduce as listReduce, scan, map as listMap } from '../../types/list/module.f.mjs'
24
+ import { keywords } from '../keywords/module.f.mjs'
19
25
  import { at, fromEntries } from '../../types/ordered_map/module.f.mjs'
20
26
  import { one } from '../../types/range/module.f.mjs'
21
27
  import {
@@ -76,7 +82,7 @@ import {
76
82
  rightCurlyBracket,
77
83
  dollarSign
78
84
  } from '../../text/ascii/module.f.mjs'
79
- import { todo, assertEq } from '../../asserts/module.f.mjs'
85
+ import { todo, assertEq, assertStructurallySame } from '../../asserts/module.f.mjs'
80
86
 
81
87
  const { fromCharCode } = String
82
88
 
@@ -156,16 +162,19 @@ const rangeId = [digitRange, ...rangeIdStart]
156
162
  const appendChar = old => input => `${old}${fromCharCode(input)}`
157
163
 
158
164
  /**
159
- * @template T
160
- * @param {_CreateToToken<T>} def
161
- * @returns {(a: _CreateToToken<T>) => (b: _CreateToToken<T>) => _CreateToToken<T>}
165
+ * @type {<T>(a: T) => Reduce<T>}
162
166
  */
163
- const union = def => a => b => {
167
+ const unionX = def => a => b => {
164
168
  if (a === def || a === b) { return b }
165
169
  if (b === def) { return a }
166
170
  throw [a, b]
167
171
  }
168
172
 
173
+ /**
174
+ * @type {<T>(a: _CreateToToken<T>) => Reduce<_CreateToToken<T>>}
175
+ */
176
+ const union = unionX
177
+
169
178
  /**
170
179
  * @template T
171
180
  * @param {_CreateToToken<T>} def
@@ -228,83 +237,32 @@ const create = def => a => {
228
237
  return v => c => x(c)(v)(c)
229
238
  }
230
239
 
231
- /** @type {(digit: number) => bigint} */
232
- const digitToBigInt = d => BigInt(d - digit0)
233
-
234
- /** @type {(digit: number) => _ParseNumberBuffer} */
235
- const startNumber = digit => ({ s: 1n, m: digitToBigInt(digit), f: 0, es: 1, e: 0 })
236
-
237
- /** @type {(digit: number) => (b: _ParseNumberBuffer) => _ParseNumberBuffer} */
238
- const addIntDigit = digit => b => ({ ...b, m: b.m * 10n + digitToBigInt(digit) })
239
-
240
- /** @type {(digit: number) => (b: _ParseNumberBuffer) => _ParseNumberBuffer} */
241
- const addFracDigit = digit => b => ({ ...b, m: b.m * 10n + digitToBigInt(digit), f: b.f - 1 })
242
-
243
- /** @type {(digit: number) => (b: _ParseNumberBuffer) => _ParseNumberBuffer} */
244
- const addExpDigit = digit => b => ({ ...b, e: b.e * 10 + digit - digit0 })
245
-
246
- /** @type {(s: _ParseNumberState) => JsToken} */
247
- const bufferToNumberToken = ({ numberKind, value, b }) => {
248
- if (numberKind === 'bigint')
249
- return { kind: 'bigint', value: b.s * b.m }
250
- return { kind: 'number', value: value, bf: [b.s * b.m, b.f + b.es * b.e] }
251
- }
240
+ /**
241
+ * Turns a completed numeric scanning state into its token.
242
+ *
243
+ * A `number` token carries the lexeme and nothing else — deriving a numeric
244
+ * value is each consumer's own policy, so no valid literal can fail to
245
+ * tokenize because its coefficient or exponent exceeds a runtime numeric
246
+ * limit. A `bigint` literal is the one case where the value *is* the token:
247
+ * `123n` means that bigint, so it is constructed here from the same lexeme.
248
+ *
249
+ * @type {(s: _ParseNumberState) => JsToken}
250
+ */
251
+ const stateToNumberToken = ({ numberKind, value }) =>
252
+ numberKind === 'bigint'
253
+ ? { kind: 'bigint', value: BigInt(value) }
254
+ : { kind: 'number', value }
252
255
 
253
256
  /**
254
- * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords
257
+ * Derived from the one source of truth for JavaScript keywords,
258
+ * `fjs/js/keywords` — FunctionalScript is a strict subset of JavaScript, so
259
+ * the tokenizer recognizes exactly that module's `keywords`.
255
260
  */
256
261
  /** @type {List<Entry<JsToken>>} */
257
- const keywordEntries = [
258
- ['arguments', { kind: 'arguments' }],
259
- ['await', { kind: 'await' }],
260
- ['break', { kind: 'break' }],
261
- ['case', { kind: 'case' }],
262
- ['catch', { kind: 'catch' }],
263
- ['class', { kind: 'class' }],
264
- ['const', { kind: 'const' }],
265
- ['continue', { kind: 'continue' }],
266
- ['debugger', { kind: 'debugger' }],
267
- ['default', { kind: 'default' }],
268
- ['delete', { kind: 'delete' }],
269
- ['do', { kind: 'do' }],
270
- ['else', { kind: 'else' }],
271
- ['enum', { kind: 'enum' }],
272
- ['eval', { kind: 'eval' }],
273
- ['export', { kind: 'export' }],
274
- ['extends', { kind: 'extends' }],
275
- ['false', { kind: 'false' }],
276
- ['finally', { kind: 'finally' }],
277
- ['for', { kind: 'for' }],
278
- ['function', { kind: 'function' }],
279
- ['if', { kind: 'if' }],
280
- ['implements', { kind: 'implements' }],
281
- ['import', { kind: 'import' }],
282
- ['in', { kind: 'in' }],
283
- ['instanceof', { kind: 'instanceof' }],
284
- ['interface', { kind: 'interface' }],
285
- ['let', { kind: 'let' }],
286
- ['new', { kind: 'new' }],
287
- ['null', { kind: 'null' }],
288
- ['package', { kind: 'package' }],
289
- ['private', { kind: 'private' }],
290
- ['protected', { kind: 'protected' }],
291
- ['public', { kind: 'public' }],
292
- ['return', { kind: 'return' }],
293
- ['static', { kind: 'static' }],
294
- ['super', { kind: 'super' }],
295
- ['switch', { kind: 'switch' }],
296
- ['this', { kind: 'this' }],
297
- ['throw', { kind: 'throw' }],
298
- ['true', { kind: 'true' }],
299
- ['try', { kind: 'try' }],
300
- ['typeof', { kind: 'typeof' }],
301
- ['undefined', { kind: 'undefined' }],
302
- ['var', { kind: 'var' }],
303
- ['void', { kind: 'void' }],
304
- ['while', { kind: 'while' }],
305
- ['with', { kind: 'with' }],
306
- ['yield', { kind: 'yield' }],
307
- ]
262
+ const keywordEntries = keywords.map(kind =>
263
+ // every keyword kind is a `JsToken` kind by construction: `_KeywordToken`
264
+ // derives its kinds from this same `keywords` list
265
+ [kind, /** @type {JsToken} */ ({ kind })])
308
266
 
309
267
  const keywordMap = fromEntries(keywordEntries)
310
268
 
@@ -383,20 +341,20 @@ const hasOperatorToken = op => at(op)(operatorMap) !== null
383
341
 
384
342
  /** @type {(state: _InitialState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
385
343
  const initialStateOp = create(
386
- /** @type {_CreateToToken<_TokenizerState>} */ (state => () => [[{ kind: 'error', message: 'unexpected character' }], state])
344
+ state => () => [[{ kind: 'error', message: 'unexpected character' }], state]
387
345
  )([
388
- rangeFunc(rangeOneNine)(/** @type {_CreateToToken<_TokenizerState>} */ (() => input => [empty, { kind: 'number', value: fromCharCode(input), b: startNumber(input), numberKind: 'int' }])),
389
- rangeSetFunc(rangeIdStart)(/** @type {_CreateToToken<_TokenizerState>} */ (() => input => [empty, { kind: 'id', value: fromCharCode(input) }])),
390
- rangeSetFunc(rangeSetWhiteSpace)(/** @type {_CreateToToken<_TokenizerState>} */ (() => () => [empty, { kind: 'ws' }])),
391
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_TokenizerState>} */ (() => () => [empty, { kind: 'nl' }])),
392
- rangeFunc(one(quotationMark))(/** @type {_CreateToToken<_TokenizerState>} */ (() => () => [empty, { kind: 'string', value: '' }])),
393
- rangeFunc(one(digit0))(/** @type {_CreateToToken<_TokenizerState>} */ (() => input => [empty, { kind: 'number', value: fromCharCode(input), b: startNumber(input), numberKind: '0' }])),
394
- rangeSetFunc(rangeOpStart)(/** @type {_CreateToToken<_TokenizerState>} */ (() => input => [empty, { kind: 'op', value: fromCharCode(input) }]))
346
+ rangeFunc(rangeOneNine)(() => input => [empty, { kind: 'number', value: fromCharCode(input), numberKind: 'int' }]),
347
+ rangeSetFunc(rangeIdStart)(() => input => [empty, { kind: 'id', value: fromCharCode(input) }]),
348
+ rangeSetFunc(rangeSetWhiteSpace)(() => () => [empty, { kind: 'ws' }]),
349
+ rangeSetFunc(rangeSetNewLine)(() => () => [empty, { kind: 'nl' }]),
350
+ rangeFunc(one(quotationMark))(() => () => [empty, { kind: 'string', value: '' }]),
351
+ rangeFunc(one(digit0))(() => input => [empty, { kind: 'number', value: fromCharCode(input), numberKind: '0' }]),
352
+ rangeSetFunc(rangeOpStart)(() => input => [empty, { kind: 'op', value: fromCharCode(input) }])
395
353
  ])
396
354
 
397
355
  /** @type {_CreateToToken<_ParseNumberState>} */
398
356
  const invalidNumberToToken = () => input => {
399
- const next = tokenizeOp(input, { kind: 'initial' })
357
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
400
358
  return [{ first: { kind: 'error', message: 'invalid number' }, tail: next[0] }, next[1]]
401
359
  }
402
360
 
@@ -404,36 +362,36 @@ const invalidNumberToToken = () => input => {
404
362
  const fullStopToToken = state => input => {
405
363
  switch (state.numberKind) {
406
364
  case '0':
407
- case 'int': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: state.b, numberKind: '.' }]
408
- default: return tokenizeOp(input, { kind: 'invalidNumber' })
365
+ case 'int': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: '.' }]
366
+ default: return tokenizeCharCodeOp(input, { kind: 'invalidNumber' })
409
367
  }
410
368
  }
411
369
 
412
370
  /** @type {_CreateToToken<_ParseNumberState>} */
413
371
  const digit0ToToken = state => input => {
414
372
  switch (state.numberKind) {
415
- case '0': return tokenizeOp(input, { kind: 'invalidNumber' })
373
+ case '0': return tokenizeCharCodeOp(input, { kind: 'invalidNumber' })
416
374
  case '.':
417
- case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addFracDigit(input)(state.b), numberKind: 'fractional' }]
375
+ case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'fractional' }]
418
376
  case 'e':
419
377
  case 'e+':
420
378
  case 'e-':
421
- case 'expDigits': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addExpDigit(input)(state.b), numberKind: 'expDigits' }]
422
- default: return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addIntDigit(input)(state.b), numberKind: state.numberKind }]
379
+ case 'expDigits': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'expDigits' }]
380
+ default: return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: state.numberKind }]
423
381
  }
424
382
  }
425
383
 
426
384
  /** @type {_CreateToToken<_ParseNumberState>} */
427
385
  const digit19ToToken = state => input => {
428
386
  switch (state.numberKind) {
429
- case '0': return tokenizeOp(input, { kind: 'invalidNumber' })
387
+ case '0': return tokenizeCharCodeOp(input, { kind: 'invalidNumber' })
430
388
  case '.':
431
- case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addFracDigit(input)(state.b), numberKind: 'fractional' }]
389
+ case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'fractional' }]
432
390
  case 'e':
433
391
  case 'e+':
434
392
  case 'e-':
435
- case 'expDigits': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addExpDigit(input)(state.b), numberKind: 'expDigits' }]
436
- default: return [empty, { kind: 'number', value: appendChar(state.value)(input), b: addIntDigit(input)(state.b), numberKind: 'int' }]
393
+ case 'expDigits': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'expDigits' }]
394
+ default: return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'int' }]
437
395
  }
438
396
  }
439
397
 
@@ -442,15 +400,15 @@ const expToToken = state => input => {
442
400
  switch (state.numberKind) {
443
401
  case '0':
444
402
  case 'int':
445
- case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: state.b, numberKind: 'e' }]
446
- default: return tokenizeOp(input, { kind: 'invalidNumber' })
403
+ case 'fractional': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'e' }]
404
+ default: return tokenizeCharCodeOp(input, { kind: 'invalidNumber' })
447
405
  }
448
406
  }
449
407
 
450
408
  /** @type {_CreateToToken<_ParseNumberState>} */
451
409
  const hyphenMinusToToken = state => input => {
452
410
  switch (state.numberKind) {
453
- case 'e': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: { ...state.b, es: -1 }, numberKind: 'e-' }]
411
+ case 'e': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'e-' }]
454
412
  default: return terminalToToken(state)(input)
455
413
  }
456
414
  }
@@ -458,8 +416,8 @@ const hyphenMinusToToken = state => input => {
458
416
  /** @type {_CreateToToken<_ParseNumberState>} */
459
417
  const plusSignToToken = state => input => {
460
418
  switch (state.numberKind) {
461
- case 'e': return [empty, { kind: 'number', value: appendChar(state.value)(input), b: state.b, numberKind: 'e+' }]
462
- default: return tokenizeOp(input, { kind: 'invalidNumber' })
419
+ case 'e': return [empty, { kind: 'number', value: appendChar(state.value)(input), numberKind: 'e+' }]
420
+ default: return tokenizeCharCodeOp(input, { kind: 'invalidNumber' })
463
421
  }
464
422
  }
465
423
 
@@ -471,13 +429,13 @@ const terminalToToken = state => input => {
471
429
  case 'e+':
472
430
  case 'e-':
473
431
  {
474
- const next = tokenizeOp(input, { kind: 'initial' })
432
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
475
433
  return [{ first: { kind: 'error', message: 'invalid number' }, tail: next[0] }, next[1]]
476
434
  }
477
435
  default:
478
436
  {
479
- const next = tokenizeOp(input, { kind: 'initial' })
480
- return [{ first: bufferToNumberToken(state), tail: next[0] }, next[1]]
437
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
438
+ return [{ first: stateToNumberToken(state), tail: next[0] }, next[1]]
481
439
  }
482
440
  }
483
441
  }
@@ -488,11 +446,11 @@ const bigintToToken = state => input => {
488
446
  case '0':
489
447
  case 'int':
490
448
  {
491
- return [empty, { kind: 'number', value: state.value, b: state.b, numberKind: 'bigint' }]
449
+ return [empty, { kind: 'number', value: state.value, numberKind: 'bigint' }]
492
450
  }
493
451
  default:
494
452
  {
495
- const next = tokenizeOp(input, { kind: 'initial' })
453
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
496
454
  return [{ first: { kind: 'error', message: 'invalid number' }, tail: next[0] }, next[1]]
497
455
  }
498
456
  }
@@ -512,12 +470,12 @@ const parseNumberStateOp = create(invalidNumberToToken)([
512
470
 
513
471
  /** @type {(state: _InvalidNumberState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
514
472
  const invalidNumberStateOp = create(
515
- /** @type {_CreateToToken<_InvalidNumberState>} */ (() => () => [empty, { kind: 'invalidNumber' }])
473
+ () => () => [empty, { kind: 'invalidNumber' }]
516
474
  )([
517
- rangeSetFunc(rangeSetTerminalForNumber)(/** @type {_CreateToToken<_InvalidNumberState>} */ (() => input => {
518
- const next = tokenizeOp(input, { kind: 'initial' })
475
+ rangeSetFunc(rangeSetTerminalForNumber)(() => input => {
476
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
519
477
  return [{ first: { kind: 'error', message: 'invalid number' }, tail: next[0] }, next[1]]
520
- }))
478
+ })
521
479
  ])
522
480
 
523
481
  /** @type {readonly NumberRange[]} */
@@ -529,34 +487,34 @@ const rangeSetStringControl = [
529
487
 
530
488
  /** @type {(state: _ParseStringState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
531
489
  const parseStringStateOp = create(
532
- /** @type {_CreateToToken<_ParseStringState>} */ (state => input => [empty, { kind: 'string', value: appendChar(state.value)(input) }])
490
+ state => input => [empty, { kind: 'string', value: appendChar(state.value)(input) }]
533
491
  )([
534
- rangeFunc(one(quotationMark))(/** @type {_CreateToToken<_ParseStringState>} */ (state => () => [[{ kind: 'string', value: state.value }], { kind: 'initial' }])),
535
- rangeFunc(one(reverseSolidus))(/** @type {_CreateToToken<_ParseStringState>} */ (state => () => [empty, { kind: 'escapeChar', value: state.value }])),
536
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseStringState>} */ (() => () => [[{ kind: 'error', message: 'unterminated string literal' }], { kind: 'nl' }])),
537
- rangeSetFunc(rangeSetStringControl)(/** @type {_CreateToToken<_ParseStringState>} */ (state => () => [[{ kind: 'error', message: 'unescaped control character in string' }], { kind: 'string', value: state.value }]))
492
+ rangeFunc(one(quotationMark))(state => () => [[{ kind: 'string', value: state.value }], { kind: 'initial' }]),
493
+ rangeFunc(one(reverseSolidus))(state => () => [empty, { kind: 'escapeChar', value: state.value }]),
494
+ rangeSetFunc(rangeSetNewLine)(() => () => [[{ kind: 'error', message: 'unterminated string literal' }], { kind: 'nl' }]),
495
+ rangeSetFunc(rangeSetStringControl)(state => () => [[{ kind: 'error', message: 'unescaped control character in string' }], { kind: 'string', value: state.value }])
538
496
  ])
539
497
 
540
498
  /** @type {_CreateToToken<_ParseEscapeCharState>} */
541
499
  const parseEscapeDefault = state => input => {
542
- const next = tokenizeOp(input, { kind: 'string', value: state.value })
500
+ const next = tokenizeCharCodeOp(input, { kind: 'string', value: state.value })
543
501
  return [{ first: { kind: 'error', message: 'unescaped character' }, tail: next[0] }, next[1]]
544
502
  }
545
503
 
546
504
  /** @type {(state: _ParseEscapeCharState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
547
505
  const parseEscapeCharStateOp = create(parseEscapeDefault)([
548
- rangeSetFunc([one(quotationMark), one(reverseSolidus), one(solidus)])(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => input => [empty, { kind: 'string', value: appendChar(state.value)(input) }])),
549
- rangeFunc(one(latinSmallLetterB))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'string', value: appendChar(state.value)(backspace) }])),
550
- rangeFunc(one(latinSmallLetterF))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'string', value: appendChar(state.value)(ff) }])),
551
- rangeFunc(one(latinSmallLetterN))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'string', value: appendChar(state.value)(lf) }])),
552
- rangeFunc(one(latinSmallLetterR))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'string', value: appendChar(state.value)(cr) }])),
553
- rangeFunc(one(latinSmallLetterT))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'string', value: appendChar(state.value)(ht) }])),
554
- rangeFunc(one(latinSmallLetterU))(/** @type {_CreateToToken<_ParseEscapeCharState>} */ (state => () => [empty, { kind: 'unicodeChar', value: state.value, unicode: 0, hexIndex: 0 }])),
506
+ rangeSetFunc([one(quotationMark), one(reverseSolidus), one(solidus)])(state => input => [empty, { kind: 'string', value: appendChar(state.value)(input) }]),
507
+ rangeFunc(one(latinSmallLetterB))(state => () => [empty, { kind: 'string', value: appendChar(state.value)(backspace) }]),
508
+ rangeFunc(one(latinSmallLetterF))(state => () => [empty, { kind: 'string', value: appendChar(state.value)(ff) }]),
509
+ rangeFunc(one(latinSmallLetterN))(state => () => [empty, { kind: 'string', value: appendChar(state.value)(lf) }]),
510
+ rangeFunc(one(latinSmallLetterR))(state => () => [empty, { kind: 'string', value: appendChar(state.value)(cr) }]),
511
+ rangeFunc(one(latinSmallLetterT))(state => () => [empty, { kind: 'string', value: appendChar(state.value)(ht) }]),
512
+ rangeFunc(one(latinSmallLetterU))(state => () => [empty, { kind: 'unicodeChar', value: state.value, unicode: 0, hexIndex: 0 }]),
555
513
  ])
556
514
 
557
515
  /** @type {_CreateToToken<_ParseUnicodeCharState>} */
558
516
  const parseUnicodeCharDefault = state => input => {
559
- const next = tokenizeOp(input, { kind: 'string', value: state.value })
517
+ const next = tokenizeCharCodeOp(input, { kind: 'string', value: state.value })
560
518
  return [{ first: { kind: 'error', message: 'invalid hex value' }, tail: next[0] }, next[1]]
561
519
  }
562
520
 
@@ -582,13 +540,13 @@ const idToToken = s => at(s)(keywordMap) ?? { kind: 'id', value: s }
582
540
  /** @type {_CreateToToken<_ParseIdState>} */
583
541
  const parseIdDefault = state => input => {
584
542
  const keyWordToken = idToToken(state.value)
585
- const next = tokenizeOp(input, { kind: 'initial' })
543
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
586
544
  return [{ first: keyWordToken, tail: next[0] }, next[1]]
587
545
  }
588
546
 
589
547
  /** @type {(state: _ParseIdState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
590
548
  const parseIdStateOp = create(parseIdDefault)([
591
- rangeSetFunc(rangeId)(/** @type {_CreateToToken<_ParseIdState>} */ (state => input => [empty, { kind: 'id', value: appendChar(state.value)(input) }]))
549
+ rangeSetFunc(rangeId)(state => input => [empty, { kind: 'id', value: appendChar(state.value)(input) }])
592
550
  ])
593
551
 
594
552
  /** @type {(state: _ParseOperatorState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
@@ -600,7 +558,7 @@ const parseOperatorStateOp = state => input => {
600
558
  default: {
601
559
  if (hasOperatorToken(nextStateValue))
602
560
  return [empty, { kind: 'op', value: nextStateValue }]
603
- const next = tokenizeOp(input, { kind: 'initial' })
561
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
604
562
  return [{ first: getOperatorToken(state.value), tail: next[0] }, next[1]]
605
563
  }
606
564
  }
@@ -608,59 +566,81 @@ const parseOperatorStateOp = state => input => {
608
566
 
609
567
  /** @type {(state: _ParseCommentState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
610
568
  const parseSinglelineCommentStateOp = create(
611
- /** @type {_CreateToToken<_ParseCommentState>} */ (state => input => [empty, { ...state, value: appendChar(state.value)(input) }])
569
+ state => input => [empty, { ...state, value: appendChar(state.value)(input) }]
612
570
  )([
613
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseCommentState>} */ (state => () => [[{ kind: '//', value: state.value }], { kind: 'nl' }]))
571
+ rangeSetFunc(rangeSetNewLine)(state => () => [[{ kind: '//', value: state.value }], { kind: 'nl' }])
614
572
  ])
615
573
 
616
574
  /** @type {(state: _ParseCommentState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
617
575
  const parseMultilineCommentStateOp = create(
618
- /** @type {_CreateToToken<_ParseCommentState>} */ (state => input => [empty, { ...state, value: appendChar(state.value)(input) }])
576
+ state => input => [empty, { ...state, value: appendChar(state.value)(input) }]
619
577
  )([
620
- rangeFunc(one(asterisk))(/** @type {_CreateToToken<_ParseCommentState>} */ (state => () => [empty, { ...state, kind: '/**' }])),
621
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseCommentState>} */ (state => input => [empty, { ...state, value: appendChar(state.value)(input), newLine: true }])),
578
+ rangeFunc(one(asterisk))(state => () => [empty, { ...state, kind: '/**' }]),
579
+ rangeSetFunc(rangeSetNewLine)(state => input => [empty, { ...state, value: appendChar(state.value)(input), newLine: true }]),
622
580
  ])
623
581
 
624
582
  /** @type {(state: _ParseCommentState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
625
583
  const parseMultilineCommentAsteriskStateOp = create(
626
- /** @type {_CreateToToken<_ParseCommentState>} */ (state => input => [empty, { ...state, kind: '/*', value: appendChar(appendChar(state.value)(asterisk))(input) }])
584
+ state => input => [empty, { ...state, kind: '/*', value: appendChar(appendChar(state.value)(asterisk))(input) }]
627
585
  )([
628
- rangeFunc(one(asterisk))(/** @type {_CreateToToken<_ParseCommentState>} */ (state => () => [empty, { ...state, value: appendChar(state.value)(asterisk) }])),
629
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseCommentState>} */ (state => input => [empty, { kind: '/*', value: appendChar(appendChar(state.value)(asterisk))(input), newLine: true }])),
630
- rangeFunc(one(solidus))(/** @type {_CreateToToken<_ParseCommentState>} */ (state => () => {
586
+ rangeFunc(one(asterisk))(state => () => [empty, { ...state, value: appendChar(state.value)(asterisk) }]),
587
+ rangeSetFunc(rangeSetNewLine)(state => input => [empty, { kind: '/*', value: appendChar(appendChar(state.value)(asterisk))(input), newLine: true }]),
588
+ rangeFunc(one(solidus))(state => () => {
631
589
  /** @type {List<JsToken>} */
632
590
  const tokens = state.newLine ? [{ kind: '/*', value: state.value }, { kind: 'nl' }] : [{ kind: '/*', value: state.value }]
633
591
  return [tokens, { kind: 'initial' }]
634
- }))
592
+ })
635
593
  ])
636
594
 
595
+ /**
596
+ * The coalescing rule for whitespace/newline trivia: a maximal run collapses
597
+ * to a single token, and a run containing any newline is an `nl`. Equal kinds
598
+ * coalesce; `nl` absorbs `ws`.
599
+ *
600
+ * Exported because `fjs/djs/tokenizer` produces the same token stream and must
601
+ * agree byte for byte — its scanner reaches the same four decisions from
602
+ * grammar tags. This module defines `JsToken`, so the rule is stated here once
603
+ * rather than re-derived on each side with only the proofs to catch a drift.
604
+ *
605
+ * @type {(a: TriviaKind, b: TriviaKind) => TriviaKind}
606
+ */
607
+ export const mergeTrivia = (a, b) => a === 'nl' || b === 'nl' ? 'nl' : 'ws'
608
+
609
+ /**
610
+ * The two trivia states, shared rather than rebuilt, so a run of trivia
611
+ * allocates nothing per character.
612
+ *
613
+ * @type {{ readonly ws: _ParseWhitespaceState, readonly nl: _ParseNewLineState }}
614
+ */
615
+ const triviaState = { ws: { kind: 'ws' }, nl: { kind: 'nl' } }
616
+
637
617
  /** @type {_CreateToToken<_ParseWhitespaceState>} */
638
618
  const parseWhitespaceDefault = () => input => {
639
- const next = tokenizeOp(input, { kind: 'initial' })
619
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
640
620
  return [{ first: { kind: 'ws' }, tail: next[0] }, next[1]]
641
621
  }
642
622
 
643
623
  /** @type {(state: _ParseWhitespaceState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
644
624
  const parseWhitespaceStateOp = create(parseWhitespaceDefault)([
645
- rangeSetFunc(rangeSetWhiteSpace)(/** @type {_CreateToToken<_ParseWhitespaceState>} */ (state => () => [empty, state])),
646
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseWhitespaceState>} */ (() => () => [empty, { kind: 'nl' }]))
625
+ rangeSetFunc(rangeSetWhiteSpace)(({ kind }) => () => [empty, triviaState[mergeTrivia(kind, 'ws')]]),
626
+ rangeSetFunc(rangeSetNewLine)(({ kind }) => () => [empty, triviaState[mergeTrivia(kind, 'nl')]])
647
627
  ])
648
628
 
649
629
  /** @type {_CreateToToken<_ParseNewLineState>} */
650
630
  const parseNewLineDefault = () => input => {
651
- const next = tokenizeOp(input, { kind: 'initial' })
631
+ const next = tokenizeCharCodeOp(input, { kind: 'initial' })
652
632
  return [{ first: { kind: 'nl' }, tail: next[0] }, next[1]]
653
633
  }
654
634
 
655
635
  /** @type {(state: _ParseNewLineState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
656
636
  const parseNewLineStateOp = create(parseNewLineDefault)([
657
- rangeSetFunc(rangeSetWhiteSpace)(/** @type {_CreateToToken<_ParseNewLineState>} */ (state => () => [empty, state])),
658
- rangeSetFunc(rangeSetNewLine)(/** @type {_CreateToToken<_ParseNewLineState>} */ (state => () => [empty, state]))
637
+ rangeSetFunc(rangeSetWhiteSpace)(({ kind }) => () => [empty, triviaState[mergeTrivia(kind, 'ws')]]),
638
+ rangeSetFunc(rangeSetNewLine)(({ kind }) => () => [empty, triviaState[mergeTrivia(kind, 'nl')]])
659
639
  ])
660
640
 
661
641
  /** @type {(state: _EofState) => (input: number) => readonly [List<JsToken>, _TokenizerState]} */
662
642
  const eofStateOp = create(
663
- /** @type {_CreateToToken<_EofState>} */ (state => () => [[{ kind: 'error', message: 'eof' }], state])
643
+ state => () => [[{ kind: 'error', message: 'eof' }], state]
664
644
  )([])
665
645
 
666
646
  /** @type {StateScan<number, _TokenizerState, List<JsToken>>} */
@@ -699,7 +679,7 @@ const tokenizeEofOp = state => {
699
679
  case 'e+':
700
680
  case 'e-': return [[{ kind: 'error', message: 'invalid number' }, { kind: 'eof' }], { kind: 'eof', }]
701
681
  }
702
- return [[bufferToNumberToken(state), { kind: 'eof' }], { kind: 'eof' }]
682
+ return [[stateToNumberToken(state), { kind: 'eof' }], { kind: 'eof' }]
703
683
  case 'op': return [[getOperatorToken(state.value), { kind: 'eof' }], { kind: 'eof' }]
704
684
  case '//': return [[{ kind: '//', value: state.value }, { kind: 'eof' }], { kind: 'eof' }]
705
685
  case '/*':
@@ -710,9 +690,6 @@ const tokenizeEofOp = state => {
710
690
  }
711
691
  }
712
692
 
713
- /** @type {StateScan<_CharCodeOrEof, _TokenizerState, List<JsToken>>} */
714
- const tokenizeOp = (input, state) => input === null ? tokenizeEofOp(state) : tokenizeCharCodeOp(input, state)
715
-
716
693
  /** @type {(metadata: TokenMetadata) => (token: JsToken) => JsTokenWithMetadata} */
717
694
  const mapTokenWithMetadata = metadata => token => { return { token, metadata } }
718
695
 
@@ -746,14 +723,23 @@ export const proof = {
746
723
  const result = getOperatorToken('@')
747
724
  assertEq(result.kind, 'error')
748
725
  },
726
+ // `tokenize` appends exactly one trailing `null` after its input, so the
727
+ // scan reaches `{ kind: 'eof' }` only on that final step — nothing ever
728
+ // runs tokenizeCharCodeOp/tokenizeEofOp again afterward with that state.
729
+ // Call each directly to cover their otherwise-unreachable `'eof'` arms.
730
+ tokenizeCharCodeOpAfterEof: () => {
731
+ const [tokens, state] = tokenizeCharCodeOp('a'.charCodeAt(0), { kind: 'eof' })
732
+ assertStructurallySame(toArray(tokens), [{ kind: 'error', message: 'eof' }])
733
+ assertStructurallySame(state, { kind: 'eof' })
734
+ },
735
+ tokenizeEofOpAfterEof: () => {
736
+ const [tokens, state] = tokenizeEofOp({ kind: 'eof' })
737
+ assertStructurallySame(toArray(tokens), [{ kind: 'error', message: 'eof' }, { kind: 'eof' }])
738
+ assertStructurallySame(state, { kind: 'eof' })
739
+ },
749
740
  throw: {
750
741
  // union throws when two distinct non-default handlers are merged for the same range;
751
742
  // this path is unreachable through the public API (no overlapping ranges in practice).
752
- unionConflict: () => {
753
- const def = (/** @type {undefined} */ _) => todo
754
- const a = (/** @type {undefined} */ _) => todo
755
- const b = (/** @type {undefined} */ _) => todo
756
- union(def)(a)(b)
757
- }
743
+ unionConflict: () => unionX(0)(1)(2)
758
744
  }
759
745
  }
@@ -2,6 +2,7 @@
2
2
  * @import { JsToken, JsTokenWithMetadata } from './types.ts'
3
3
  */
4
4
  export declare const proof: {
5
+ mergeTrivia: (() => void)[];
5
6
  djs: (() => void)[];
6
7
  operators: (() => void)[];
7
8
  ws: (() => void)[];