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
@@ -4,13 +4,22 @@
4
4
  * Built from the serializable IR in `fjs/bnf/data`, this is one member of the
5
5
  * family of automaton builders that consume a {@link RuleSet}: it compiles the
6
6
  * grammar into a predictive {@link dispatchMap} and matches input into an AST
7
- * ({@link MatchResult}). It throws at build time (`can not merge …`) when the
8
- * grammar is not LL(1) a first/first conflict. Nullability is looked up from
9
- * {@link emptyTagMap} in `fjs/bnf/data` rather than re-derived here.
7
+ * ({@link MatchResult}). It throws at build time when the grammar is not
8
+ * LL(1): `can not merge …` for a first/first conflict, `left recursion …` for
9
+ * a rule reachable from itself in first position. Nullability is looked up
10
+ * from {@link emptyTagMap} in `fjs/bnf/data` rather than re-derived here.
11
+ *
12
+ * A rule is entered *before* its first symbol is consumed: the dispatch map
13
+ * only selects — a variant's branch, a repetition's next round — and every
14
+ * rule invocation builds a node of its own, so the AST is the one
15
+ * `fjs/bnf/descent` builds for the same grammar (see ./README.md). A `Repeat`
16
+ * rule is matched iteratively and produces one node holding a flat sequence
17
+ * of the items it matched.
10
18
  *
11
19
  * The caller passes physical symbols only; the matcher synthesizes the one
12
- * logical EOF ({@link eofSymbol}) after them, so a grammar can dispatch on the
13
- * end of input with the `eof` terminal.
20
+ * logical EOF after them, so a grammar can dispatch on the end of input with
21
+ * the `eof` terminal. The position it does that at, the AST it builds, and the
22
+ * constructors that pair them are the shared layer in `fjs/bnf/matcher`.
14
23
  *
15
24
  * See `./types.ts` for the type-level API.
16
25
  *
@@ -19,16 +28,35 @@
19
28
  * @import { CodePoint } from '../../text/utf16/types.ts'
20
29
  * @import { Properties } from '../../types/range_map/types.ts'
21
30
  * @import { StringSet } from '../../types/string_set/types.ts'
22
- * @import { EmptyTag, RuleSet } from '../data/types.ts'
31
+ * @import { List } from '../../types/list/types.ts'
32
+ * @import { RuleSet, Sequence } from '../data/types.ts'
33
+ * @import { Ast, AstResult, AstSequence, AstTag, Cursor } from '../matcher/types.ts'
23
34
  * @import { Rule as FRule } from '../types.ts'
24
- * @import { AstSequence, AstTag, Match, MatchResult, Remainder, _AstRule, _Dispatch, _DispatchMap, _DispatchResult, _DispatchRule } from './types.ts'
35
+ * @import { Match, MatchResult, Remainder, _Dispatch, _DispatchBranch, _DispatchMap, _DispatchResult, _DispatchRule } from './types.ts'
25
36
  */
26
- import type { RuleSet } from '../data/types.ts';
37
+ import type { CodePoint } from '../../text/utf16/types.ts';
38
+ import type { List } from '../../types/list/types.ts';
39
+ import type { RuleSet, Sequence } from '../data/types.ts';
40
+ import type { Ast, AstResult, AstSequence, AstTag, Cursor } from '../matcher/types.ts';
27
41
  import type { Rule as FRule } from '../types.ts';
28
- import type { AstSequence, AstTag, Match, _AstRule, _DispatchMap, _DispatchRule } from './types.ts';
42
+ import type { Match, _DispatchMap } from './types.ts';
29
43
  /**
30
44
  * Builds a dispatch map for a {@link RuleSet} to enable predictive parsing.
31
45
  *
46
+ * Each rule's entry holds the rule's first set as a range map. Only a variant
47
+ * reads the value under a symbol — the branch that lookahead selects; every
48
+ * other reader asks whether a symbol is in the set at all, which is how a
49
+ * repetition decides to start another round. `empty` is the branch a variant
50
+ * takes when no entry matches the lookahead — its nullable branch, absent
51
+ * when the variant cannot match empty.
52
+ *
53
+ * A rule referenced in first position while its own first set is still being
54
+ * computed is left recursion — a grammar no lookahead can decide, whose match
55
+ * would loop at the same position forever — so building throws
56
+ * (`left recursion …`) rather than letting the matcher diverge. The one
57
+ * self-reference with a sound reading is a repetition of itself: zero rounds
58
+ * match whatever the item does, so it dispatches on nothing and matches empty.
59
+ *
32
60
  * @type {(ruleSet: RuleSet) => _DispatchMap}
33
61
  */
34
62
  export declare const dispatchMap: (ruleSet: RuleSet) => _DispatchMap;
@@ -38,27 +66,41 @@ export declare const dispatchMap: (ruleSet: RuleSet) => _DispatchMap;
38
66
  * @type {(fr: FRule) => Match}
39
67
  */
40
68
  export declare const parser: (fr: FRule) => Match;
41
- export type _Cursor = number;
42
- export type _Position = _Cursor | null;
43
- export type _Result = {
44
- readonly ast: _AstRule;
45
- readonly success: boolean;
46
- readonly pos: _Position;
69
+ export type _Position = Cursor | null;
70
+ export type _Result = AstResult<CodePoint, _Position>;
71
+ export type _SeqFrame = {
72
+ readonly kind: 'seq';
73
+ readonly tag: AstTag;
74
+ readonly items: Sequence;
75
+ readonly itemIndex: number;
76
+ readonly seq: AstSequence<CodePoint>;
47
77
  };
48
- export type _Frame = {
78
+ export type _RepeatFrame = {
79
+ readonly kind: 'repeat';
49
80
  readonly tag: AstTag;
50
- readonly rules: readonly string[];
51
- readonly ruleIndex: number;
52
- readonly seq: AstSequence;
81
+ readonly item: string;
82
+ readonly items: _Items;
53
83
  };
84
+ export type _Items = List<Ast<CodePoint>>;
85
+ export type _Frame = _SeqFrame | _RepeatFrame;
54
86
  export type _Stack = null | {
55
87
  readonly top: _Frame;
56
88
  readonly rest: _Stack;
57
89
  };
58
- export type _Task = {
59
- readonly rule: _DispatchRule;
60
- readonly pos: _Cursor;
90
+ export type _RuleTask = {
91
+ readonly kind: 'rule';
92
+ readonly name: string;
93
+ readonly tag: AstTag;
94
+ readonly pos: Cursor;
95
+ };
96
+ export type _RepeatTask = {
97
+ readonly kind: 'repeat';
98
+ readonly tag: AstTag;
99
+ readonly item: string;
100
+ readonly items: _Items;
101
+ readonly pos: Cursor;
61
102
  };
103
+ export type _Task = _RuleTask | _RepeatTask;
62
104
  /**
63
105
  * Creates an LL(1) parser from an already materialized {@link RuleSet}.
64
106
  *
@@ -4,13 +4,22 @@
4
4
  * Built from the serializable IR in `fjs/bnf/data`, this is one member of the
5
5
  * family of automaton builders that consume a {@link RuleSet}: it compiles the
6
6
  * grammar into a predictive {@link dispatchMap} and matches input into an AST
7
- * ({@link MatchResult}). It throws at build time (`can not merge …`) when the
8
- * grammar is not LL(1) a first/first conflict. Nullability is looked up from
9
- * {@link emptyTagMap} in `fjs/bnf/data` rather than re-derived here.
7
+ * ({@link MatchResult}). It throws at build time when the grammar is not
8
+ * LL(1): `can not merge …` for a first/first conflict, `left recursion …` for
9
+ * a rule reachable from itself in first position. Nullability is looked up
10
+ * from {@link emptyTagMap} in `fjs/bnf/data` rather than re-derived here.
11
+ *
12
+ * A rule is entered *before* its first symbol is consumed: the dispatch map
13
+ * only selects — a variant's branch, a repetition's next round — and every
14
+ * rule invocation builds a node of its own, so the AST is the one
15
+ * `fjs/bnf/descent` builds for the same grammar (see ./README.md). A `Repeat`
16
+ * rule is matched iteratively and produces one node holding a flat sequence
17
+ * of the items it matched.
10
18
  *
11
19
  * The caller passes physical symbols only; the matcher synthesizes the one
12
- * logical EOF ({@link eofSymbol}) after them, so a grammar can dispatch on the
13
- * end of input with the `eof` terminal.
20
+ * logical EOF after them, so a grammar can dispatch on the end of input with
21
+ * the `eof` terminal. The position it does that at, the AST it builds, and the
22
+ * constructors that pair them are the shared layer in `fjs/bnf/matcher`.
14
23
  *
15
24
  * See `./types.ts` for the type-level API.
16
25
  *
@@ -19,18 +28,24 @@
19
28
  * @import { CodePoint } from '../../text/utf16/types.ts'
20
29
  * @import { Properties } from '../../types/range_map/types.ts'
21
30
  * @import { StringSet } from '../../types/string_set/types.ts'
22
- * @import { EmptyTag, RuleSet } from '../data/types.ts'
31
+ * @import { List } from '../../types/list/types.ts'
32
+ * @import { RuleSet, Sequence } from '../data/types.ts'
33
+ * @import { Ast, AstResult, AstSequence, AstTag, Cursor } from '../matcher/types.ts'
23
34
  * @import { Rule as FRule } from '../types.ts'
24
- * @import { AstSequence, AstTag, Match, MatchResult, Remainder, _AstRule, _Dispatch, _DispatchMap, _DispatchResult, _DispatchRule } from './types.ts'
35
+ * @import { Match, MatchResult, Remainder, _Dispatch, _DispatchBranch, _DispatchMap, _DispatchResult, _DispatchRule } from './types.ts'
25
36
  */
26
37
 
27
38
  import { strictEqual } from '../../types/function/operator/module.f.mjs'
28
- import { toArray } from '../../types/list/module.f.mjs'
39
+ import { assertNotNullish } from '../../asserts/module.f.mjs'
40
+ import { identity } from '../../types/function/module.f.mjs'
41
+ import { concat, toArray } from '../../types/list/module.f.mjs'
29
42
  import { rangeMap } from '../../types/range_map/module.f.mjs'
43
+ import { contains as rangeContains } from '../../types/range/module.f.mjs'
30
44
  import { contains, set } from '../../types/string_set/module.f.mjs'
31
- import { eofSymbol, rangeDecode } from '../module.f.mjs'
45
+ import { rangeDecode } from '../module.f.mjs'
32
46
  import { definedEntries } from '../../types/object/module.f.mjs'
33
- import { emptyTagMap, toData } from '../data/module.f.mjs'
47
+ import { emptyTagMap, isRepeat, toData } from '../data/module.f.mjs'
48
+ import { leafAt, mrFail, mrSuccess, physicalIdx, symbolAt } from '../matcher/module.f.mjs'
34
49
 
35
50
  /** @type {Properties<_DispatchResult>} */
36
51
  const dispatchProps = {
@@ -52,83 +67,81 @@ const dispatchOp = rangeMap(dispatchProps)
52
67
  /**
53
68
  * Builds a dispatch map for a {@link RuleSet} to enable predictive parsing.
54
69
  *
70
+ * Each rule's entry holds the rule's first set as a range map. Only a variant
71
+ * reads the value under a symbol — the branch that lookahead selects; every
72
+ * other reader asks whether a symbol is in the set at all, which is how a
73
+ * repetition decides to start another round. `empty` is the branch a variant
74
+ * takes when no entry matches the lookahead — its nullable branch, absent
75
+ * when the variant cannot match empty.
76
+ *
77
+ * A rule referenced in first position while its own first set is still being
78
+ * computed is left recursion — a grammar no lookahead can decide, whose match
79
+ * would loop at the same position forever — so building throws
80
+ * (`left recursion …`) rather than letting the matcher diverge. The one
81
+ * self-reference with a sound reading is a repetition of itself: zero rounds
82
+ * match whatever the item does, so it dispatches on nothing and matches empty.
83
+ *
55
84
  * @type {(ruleSet: RuleSet) => _DispatchMap}
56
85
  */
57
86
  export const dispatchMap = ruleSet => {
58
87
 
59
88
  const nullMap = emptyTagMap(ruleSet)
60
89
 
61
- /** @type {(dr: _DispatchResult, name: string) => _DispatchResult} */
62
- const addRuleToDispatch = (dr, name) => {
63
- if (dr === null)
64
- return null
65
-
66
- return { tag: dr.tag, rules: [...dr.rules, name]}
67
- }
68
-
69
- /** @type {(dr: _DispatchResult, tag: string) => _DispatchResult} */
70
- const addTagToDispatch = (dr, tag) => {
71
- if (dr === null)
72
- return null
73
-
74
- return { tag, rules: dr.rules}
75
- }
76
-
77
90
  /** @type {(dm: _DispatchMap, name: string, current: StringSet) => _DispatchMap} */
78
91
  const dispatchRule = (dm, name, current) => {
79
92
  if (name in dm) { return dm }
80
93
  const newCurrent = set(name)(current)
81
94
  const rule = ruleSet[name]
95
+ /** @type {_DispatchRule} */
96
+ let dr
82
97
  if (typeof rule === 'number') {
83
- const range = rangeDecode(rule)
84
- const dispatch = dispatchOp.fromRange({tag: undefined, rules: []})(range)
85
- /** @type {_DispatchRule} */
86
- const dr = {emptyTag: undefined, rangeMap: dispatch}
87
- return { ...dm, [name]: dr }
98
+ dr = {
99
+ empty: undefined,
100
+ rangeMap: dispatchOp.fromRange({ tag: undefined, name })(rangeDecode(rule)),
101
+ }
88
102
  } else if (rule instanceof Array) {
89
- /** @type {EmptyTag} */
90
- let emptyTag = true
103
+ // A sequence's first set is the union of its items' first sets up
104
+ // to and including the first item that cannot match empty.
91
105
  /** @type {_Dispatch} */
92
- let result = []
106
+ let first = []
93
107
  for (const item of rule) {
94
108
  if (contains(item)(newCurrent)) {
95
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]])
96
- } else {
97
- dm = dispatchRule(dm, item, newCurrent)
98
- const dr = /** @type {_DispatchRule} */ (dm[item])
99
- if (emptyTag === true) {
100
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]])
101
- result = toArray(dispatchOp.merge(result)(dr.rangeMap))
102
- emptyTag = nullMap[item] !== undefined ? true : undefined
103
- } else {
104
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]])
105
- }
109
+ throw ['left recursion [', name, '][', item, ']']
106
110
  }
111
+ dm = dispatchRule(dm, item, newCurrent)
112
+ first = toArray(dispatchOp.merge(first)(assertNotNullish(dm[item]).rangeMap))
113
+ if (nullMap[item] === undefined) { break }
107
114
  }
108
- /** @type {_DispatchRule} */
109
- const dr = {emptyTag, rangeMap: result}
110
- return { ...dm, [name]: dr}
115
+ dr = { empty: undefined, rangeMap: first }
116
+ } else if (isRepeat(rule)) {
117
+ // Zero rounds always match, so a repetition needs no `empty`
118
+ // branch; its first set is its item's. A repetition of itself has
119
+ // no first set to dispatch on, so it can only match zero rounds —
120
+ // `toData` never derives one, but a hand-written rule set can.
121
+ dm = contains(rule)(newCurrent) ? dm : dispatchRule(dm, rule, newCurrent)
122
+ const itemDr = dm[rule]
123
+ dr = { empty: undefined, rangeMap: itemDr === undefined ? [] : itemDr.rangeMap }
111
124
  } else {
112
- const entries = definedEntries(rule)
113
125
  /** @type {_Dispatch} */
114
- let result = []
115
- /** @type {EmptyTag} */
116
- let emptyTag = undefined
117
- for (const [tag, item] of entries) {
126
+ let first = []
127
+ /** @type {_DispatchBranch | undefined} */
128
+ let empty = undefined
129
+ for (const [tag, item] of definedEntries(rule)) {
130
+ if (contains(item)(newCurrent)) {
131
+ throw ['left recursion [', name, '][', item, ']']
132
+ }
118
133
  dm = dispatchRule(dm, item, newCurrent)
119
- const dr = /** @type {_DispatchRule} */ (dm[item])
134
+ /** @type {_Dispatch} */
135
+ const d = assertNotNullish(dm[item]).rangeMap
136
+ .map(x => [x[0] === null ? null : { tag, name: item }, x[1]])
137
+ first = toArray(dispatchOp.merge(first)(d))
120
138
  if (nullMap[item] !== undefined) {
121
- emptyTag = tag
122
- } else {
123
- /** @type {_Dispatch} */
124
- const d = dr.rangeMap.map(x => [addTagToDispatch(x[0], tag), x[1]])
125
- result = toArray(dispatchOp.merge(result)(d))
139
+ empty = { tag, name: item }
126
140
  }
127
141
  }
128
- /** @type {_DispatchRule} */
129
- const dr = {emptyTag, rangeMap: result}
130
- return { ...dm, [name]: dr}
142
+ dr = { empty, rangeMap: first }
131
143
  }
144
+ return { ...dm, [name]: dr }
132
145
  }
133
146
 
134
147
  /** @type {_DispatchMap} */
@@ -151,48 +164,52 @@ export const parser = fr => {
151
164
  }
152
165
 
153
166
  /**
154
- * A match position over the physical input: `0 .. cp.length` are the physical
155
- * positions, and `cp.length + 1` is where the one synthesized EOF has been
156
- * consumed.
167
+ * Where a match stopped: a {@link Cursor}, or `null` when it ran out of input —
168
+ * the `null` {@link Remainder} this backend reports for that.
157
169
  *
158
- * This is the `(idx, eofConsumed)` cursor written as one number: `eofConsumed`
159
- * can only be true at the physical end, so the pair and the extended position
160
- * hold the same information.
161
- *
162
- * @typedef {number} _Cursor
170
+ * @typedef {Cursor|null} _Position
163
171
  */
164
172
 
165
173
  /**
166
- * Where a match stopped: a {@link _Cursor}, or `null` when it ran out of input
167
- * the `null` {@link Remainder} this backend reports for that.
174
+ * The machine's own result: a {@link MatchResult} positioned by a cursor
175
+ * instead of by a materialized remainder.
168
176
  *
169
- * @typedef {_Cursor|null} _Position
177
+ * @typedef {AstResult<CodePoint, _Position>} _Result
170
178
  */
171
179
 
172
180
  /**
173
- * The machine's own result: a {@link MatchResult} positioned by a cursor
174
- * instead of by a materialized remainder.
181
+ * A suspended sequence match: `items[itemIndex]` is being matched by the
182
+ * current task, and `seq` holds the ASTs of the items already matched.
175
183
  *
176
184
  * @typedef {{
177
- * readonly ast: _AstRule
178
- * readonly success: boolean
179
- * readonly pos: _Position
180
- * }} _Result
185
+ * readonly kind: 'seq'
186
+ * readonly tag: AstTag
187
+ * readonly items: Sequence
188
+ * readonly itemIndex: number
189
+ * readonly seq: AstSequence<CodePoint>
190
+ * }} _SeqFrame
181
191
  */
182
192
 
183
193
  /**
184
- * A suspended rules-chain match: `rules[ruleIndex]` is being matched by the
185
- * current task, and `seq` holds the ASTs matched so far starting with the
186
- * dispatched symbol's leaf, empty when that symbol was the synthesized EOF.
194
+ * A suspended repetition: the item is being matched by the current task for
195
+ * one more round, and `items` holds the ASTs of the rounds that already
196
+ * completed. They accumulate as a list rather than an array because a
197
+ * repetition is as long as its input: appending to an array per round would
198
+ * copy the whole prefix each time and make one repetition quadratic in the
199
+ * number of items it matched.
187
200
  *
188
201
  * @typedef {{
202
+ * readonly kind: 'repeat'
189
203
  * readonly tag: AstTag
190
- * readonly rules: readonly string[]
191
- * readonly ruleIndex: number
192
- * readonly seq: AstSequence
193
- * }} _Frame
204
+ * readonly item: string
205
+ * readonly items: _Items
206
+ * }} _RepeatFrame
194
207
  */
195
208
 
209
+ /** @typedef {List<Ast<CodePoint>>} _Items */
210
+
211
+ /** @typedef {_SeqFrame | _RepeatFrame} _Frame */
212
+
196
213
  /**
197
214
  * Immutable cons-cell stack: O(1) push/pop, no array copying per step.
198
215
  *
@@ -207,36 +224,39 @@ export const parser = fr => {
207
224
  * to resume the innermost frame instead.
208
225
  *
209
226
  * @typedef {{
210
- * readonly rule: _DispatchRule
211
- * readonly pos: _Cursor
212
- * }} _Task
227
+ * readonly kind: 'rule'
228
+ * readonly name: string
229
+ * readonly tag: AstTag
230
+ * readonly pos: Cursor
231
+ * }} _RuleTask
213
232
  */
214
233
 
215
- /** @type {(tag: AstTag, sequence: AstSequence, pos: _Position) => _Result} */
216
- const mrSuccess = (tag, sequence, pos) =>
217
- ({ast: {tag, sequence}, success: true, pos})
218
-
219
- /** @type {(tag: AstTag, sequence: AstSequence, pos: _Position) => _Result} */
220
- const mrFail = (tag, sequence, pos) =>
221
- ({ast: {tag, sequence}, success: false, pos})
222
-
223
234
  /**
224
- * The semantic symbol a cursor points at: a code point inside the physical
225
- * input, and the synthesized {@link eofSymbol} at its end. Only meaningful
226
- * where the cursor still has a symbol, `pos <= cp.length`.
235
+ * The next round of a repetition, about to be decided by lookahead. Both the
236
+ * rule that introduces a repetition and the frame that finishes one of its
237
+ * rounds go through this, so a round is set up in exactly one place.
227
238
  *
228
- * @type {(cp: readonly CodePoint[], pos: _Cursor) => number}
239
+ * @typedef {{
240
+ * readonly kind: 'repeat'
241
+ * readonly tag: AstTag
242
+ * readonly item: string
243
+ * readonly items: _Items
244
+ * readonly pos: Cursor
245
+ * }} _RepeatTask
229
246
  */
230
- const symbolAt = (cp, pos) => pos < cp.length ? cp[pos] : eofSymbol
247
+
248
+ /** @typedef {_RuleTask | _RepeatTask} _Task */
231
249
 
232
250
  /**
233
- * What consuming the symbol at a cursor contributes to the AST: the code point
234
- * itself, and nothing for the synthesized EOF it has no physical source
235
- * element.
251
+ * A leaf here is the code point itself, so it *is* its own symbol. The
252
+ * annotation pins `identity`'s type parameter, which `symbolAt`'s own cannot
253
+ * infer from a fully generic argument.
236
254
  *
237
- * @type {(cp: readonly CodePoint[], pos: _Cursor) => AstSequence}
255
+ * @type {(leaf: CodePoint) => number}
238
256
  */
239
- const leafAt = (cp, pos) => pos < cp.length ? [cp[pos]] : []
257
+ const symbolOf = identity
258
+
259
+ const symbolAtCp = symbolAt(symbolOf)
240
260
 
241
261
  /**
242
262
  * The public remainder of a position. It stays physical, so consuming EOF
@@ -244,7 +264,7 @@ const leafAt = (cp, pos) => pos < cp.length ? [cp[pos]] : []
244
264
  *
245
265
  * @type {(cp: readonly CodePoint[], pos: _Position) => Remainder}
246
266
  */
247
- const remainderAt = (cp, pos) => pos === null ? null : cp.slice(Math.min(pos, cp.length))
267
+ const remainderAt = (cp, pos) => pos === null ? null : cp.slice(physicalIdx(cp.length)(pos))
248
268
 
249
269
  /**
250
270
  * Creates an LL(1) parser from an already materialized {@link RuleSet}.
@@ -255,58 +275,87 @@ export const parserRuleSet = ruleSet => {
255
275
  const map = dispatchMap(ruleSet)
256
276
 
257
277
  /** @type {(name: string) => _DispatchRule} */
258
- const dispatched = name => /** @type {_DispatchRule} */ (map[name])
278
+ const dispatched = name => assertNotNullish(map[name])
259
279
 
260
- // The matcher as an explicit-stack machine: each iteration either starts the
261
- // current task (dispatching one rule and pushing a frame for the rules chain
262
- // that symbol selected) or feeds the pending result into the innermost frame.
263
- // Semantics are identical to the former recursive matcher, but the JS call
264
- // stack stays O(1) however deep the grammar recurses: a right-recursive rule
265
- // (e.g. a `repeat0Plus` chain) descends once per consumed code point, so the
266
- // depth used to grow with input length and overflow (see the longInput proof
280
+ // The matcher as an explicit-stack machine, the same shape as the sibling
281
+ // `bnf/descent` matcher's: each iteration either starts the current task
282
+ // (walking one rule of the grammar) or feeds the pending result into the
283
+ // innermost frame. The JS call stack stays O(1) however deeply the grammar
284
+ // recurses nesting depth grows with input length, so a few thousand code
285
+ // points used to overflow a recursive matcher (see the longInput proof
267
286
  // group). Positions are cursors into the shared input array for the same
268
287
  // reason: re-slicing the remainder per step made a match quadratic.
269
- /** @type {(dr0: _DispatchRule, cp: readonly CodePoint[]) => MatchResult} */
270
- const f = (dr0, cp) => {
288
+ //
289
+ // Unlike `bnf/descent` it never backtracks: the dispatch map decides every
290
+ // choice from the lookahead symbol, so a cursor only moves forward and no
291
+ // frame needs rewind state.
292
+ /** @type {(name: string, cp: readonly CodePoint[]) => MatchResult} */
293
+ const f = (name, cp) => {
271
294
  /** @type {_Stack} */
272
295
  let stack = null
273
296
  /** @type {_Task|null} */
274
- let task = {rule: dr0, pos: 0}
297
+ let task = {kind: 'rule', name, tag: undefined, pos: 0}
275
298
  /** @type {_Result} */
276
299
  let result = mrFail(undefined, [], 0)
277
300
 
278
301
  while (true) {
279
302
  if (task !== null) {
280
- // The explicit annotation cuts a control-flow inference cycle
303
+ // The explicit cast cuts a control-flow inference cycle
281
304
  // (TS7022): `task`'s narrowed type feeds `pos`, which feeds the
282
305
  // `task` assignment below that the narrowing depends on.
283
- /** @type {_Task} */
284
- const current = task
285
- const {rule: {emptyTag, rangeMap}, pos} = current
306
+ const current = /** @type {_Task} */ (task)
286
307
  task = null
287
- // The one logical EOF is available at the physical end, and only
288
- // there: a rule that dispatches on it consumes it, once. Past it
289
- // no symbol is left to dispatch on.
290
- const dr = pos > cp.length ? null : dispatchOp.get(rangeMap)(symbolAt(cp, pos))
291
- if (dr === null) {
292
- if (emptyTag !== undefined) {
293
- result = mrSuccess(emptyTag, [], pos)
294
- } else if (pos >= cp.length) {
295
- // Nothing left to reject: the match ran out of input.
296
- result = mrSuccess(emptyTag, [], null)
308
+ if (current.kind === 'repeat') {
309
+ const {tag, item, items, pos} = current
310
+ // One more round starts exactly while the lookahead is in
311
+ // the item's first set. Predictive selection cannot start a
312
+ // round that consumes nothing, so every round advances the
313
+ // cursor and the repetition always terminates.
314
+ if (pos <= cp.length && dispatchOp.get(dispatched(item).rangeMap)(symbolAtCp(cp, pos)) !== null) {
315
+ stack = {top: {kind: 'repeat', tag, item, items}, rest: stack}
316
+ task = {kind: 'rule', name: item, tag: undefined, pos}
297
317
  } else {
298
- result = mrFail(emptyTag, [], pos)
318
+ result = mrSuccess(tag, toArray(items), pos)
299
319
  }
300
320
  continue
301
321
  }
302
- const {tag, rules} = dr
303
- const seq = leafAt(cp, pos)
304
- const next = pos + 1
305
- if (rules.length === 0) {
306
- result = mrSuccess(tag, seq, next)
322
+ const {name, tag, pos} = current
323
+ const rule = ruleSet[name]
324
+ if (typeof rule === 'number') {
325
+ // The one logical EOF is available at the physical end, and
326
+ // only there: a terminal that matches it consumes it, once.
327
+ // Past it no symbol is left to consume.
328
+ if (pos <= cp.length && rangeContains(...rangeDecode(rule))(symbolAtCp(cp, pos))) {
329
+ result = mrSuccess(tag, leafAt(cp, pos), pos + 1)
330
+ } else if (pos >= cp.length) {
331
+ // Nothing left to reject: the match ran out of input.
332
+ result = mrSuccess(undefined, [], null)
333
+ } else {
334
+ result = mrFail(undefined, [], pos)
335
+ }
336
+ } else if (rule instanceof Array) {
337
+ if (rule.length === 0) {
338
+ result = mrSuccess(tag, [], pos)
339
+ } else {
340
+ stack = {top: {kind: 'seq', tag, items: rule, itemIndex: 0, seq: []}, rest: stack}
341
+ task = {kind: 'rule', name: rule[0], tag: undefined, pos}
342
+ }
343
+ } else if (isRepeat(rule)) {
344
+ task = {kind: 'repeat', tag, item: rule, items: null, pos}
307
345
  } else {
308
- stack = {top: {tag, rules, ruleIndex: 0, seq}, rest: stack}
309
- task = {rule: dispatched(rules[0]), pos: next}
346
+ const {empty, rangeMap} = dispatched(name)
347
+ const d = pos > cp.length ? null : dispatchOp.get(rangeMap)(symbolAtCp(cp, pos))
348
+ const branch = d ?? empty
349
+ if (branch !== undefined) {
350
+ // The selected branch's node becomes the variant's,
351
+ // carrying the branch's tag — entered before its first
352
+ // symbol is consumed, like every rule invocation.
353
+ task = {kind: 'rule', name: branch.name, tag: branch.tag, pos}
354
+ } else if (pos >= cp.length) {
355
+ result = mrSuccess(undefined, [], null)
356
+ } else {
357
+ result = mrFail(undefined, [], pos)
358
+ }
310
359
  }
311
360
  continue
312
361
  }
@@ -319,23 +368,34 @@ export const parserRuleSet = ruleSet => {
319
368
  stack = stack.rest
320
369
 
321
370
  const {ast, success, pos} = result
322
- // A failing item's own result propagates unchanged, exactly as the
323
- // recursive version returned it through the chain.
371
+ // A failing rule fails everything above it: LL(1) committed to
372
+ // every choice on the way here, so there is nothing to rewind and
373
+ // retry, and the failure propagates unchanged — its position is
374
+ // where matching stopped.
324
375
  if (success === false) { continue }
325
- const seq = [...frame.seq, ast]
326
- if (pos === null) {
327
- result = mrSuccess(frame.tag, seq, null)
328
- continue
329
- }
330
- const ruleIndex = frame.ruleIndex + 1
331
- if (ruleIndex < frame.rules.length) {
332
- stack = {top: {...frame, ruleIndex, seq}, rest: stack}
333
- task = {rule: dispatched(frame.rules[ruleIndex]), pos}
376
+ if (frame.kind === 'seq') {
377
+ const seq = [...frame.seq, ast]
378
+ if (pos === null) {
379
+ result = mrSuccess(frame.tag, seq, null)
380
+ } else {
381
+ const itemIndex = frame.itemIndex + 1
382
+ if (itemIndex < frame.items.length) {
383
+ stack = {top: {...frame, itemIndex, seq}, rest: stack}
384
+ task = {kind: 'rule', name: frame.items[itemIndex], tag: undefined, pos}
385
+ } else {
386
+ result = mrSuccess(frame.tag, seq, pos)
387
+ }
388
+ }
334
389
  } else {
335
- result = mrSuccess(frame.tag, seq, pos)
390
+ const items = concat(frame.items)([ast])
391
+ if (pos === null) {
392
+ result = mrSuccess(frame.tag, toArray(items), null)
393
+ } else {
394
+ task = {kind: 'repeat', tag: frame.tag, item: frame.item, items, pos}
395
+ }
336
396
  }
337
397
  }
338
398
  }
339
399
 
340
- return (name, cp) => f(dispatched(name), cp)
400
+ return f
341
401
  }