kibi-cli 2.31.1 → 2.35.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 (302) hide show
  1. package/dist/cli-register-proof.d.ts.map +1 -1
  2. package/dist/cli-register-proof.js +19 -7
  3. package/dist/cli-register-reporting.d.ts.map +1 -1
  4. package/dist/cli-register-reporting.js +2 -0
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +5 -1
  7. package/dist/commands/check-generated.d.ts +5 -1
  8. package/dist/commands/check-generated.d.ts.map +1 -1
  9. package/dist/commands/check-generated.js +24 -35
  10. package/dist/commands/check.d.ts +1 -0
  11. package/dist/commands/check.d.ts.map +1 -1
  12. package/dist/commands/check.js +14 -5
  13. package/dist/commands/discovery-table.d.ts.map +1 -1
  14. package/dist/commands/discovery-table.js +51 -4
  15. package/dist/commands/doctor.js +19 -4
  16. package/dist/commands/init-helpers.d.ts.map +1 -1
  17. package/dist/commands/init-helpers.js +8 -5
  18. package/dist/commands/migrate.d.ts.map +1 -1
  19. package/dist/commands/migrate.js +109 -5
  20. package/dist/commands/prove.d.ts +48 -1
  21. package/dist/commands/prove.d.ts.map +1 -1
  22. package/dist/commands/prove.js +293 -8
  23. package/dist/commands/report.d.ts +1 -0
  24. package/dist/commands/report.d.ts.map +1 -1
  25. package/dist/commands/report.js +70 -5
  26. package/dist/commands/signoff.d.ts.map +1 -1
  27. package/dist/commands/signoff.js +20 -1
  28. package/dist/commands/sync/manifest.d.ts +10 -1
  29. package/dist/commands/sync/manifest.d.ts.map +1 -1
  30. package/dist/commands/sync/manifest.js +106 -17
  31. package/dist/commands/sync/persistence.d.ts.map +1 -1
  32. package/dist/commands/sync/persistence.js +16 -5
  33. package/dist/commands/sync.d.ts.map +1 -1
  34. package/dist/commands/sync.js +19 -0
  35. package/dist/diagnostics.js +1 -1
  36. package/dist/engine.js +1 -1
  37. package/dist/extractors/markdown.d.ts +4 -2
  38. package/dist/extractors/markdown.d.ts.map +1 -1
  39. package/dist/extractors/markdown.js +17 -0
  40. package/dist/extractors/symbol-coordinates.js +2 -2
  41. package/dist/extractors/symbols-coordinator.d.ts +2 -4
  42. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  43. package/dist/extractors/symbols-coordinator.js +1 -2
  44. package/dist/operations/bootstrap/candidate-helpers.d.ts +3 -0
  45. package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
  46. package/dist/operations/bootstrap/candidate-helpers.js +4 -0
  47. package/dist/operations/migration/evidence-floor.d.ts +41 -0
  48. package/dist/operations/migration/evidence-floor.d.ts.map +1 -0
  49. package/dist/operations/migration/evidence-floor.js +230 -0
  50. package/dist/operations/migration/proof-contract-claims.d.ts +72 -0
  51. package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -0
  52. package/dist/operations/migration/proof-contract-claims.js +215 -0
  53. package/dist/operations/migration/schema6.d.ts +6 -0
  54. package/dist/operations/migration/schema6.d.ts.map +1 -1
  55. package/dist/operations/migration/schema6.js +92 -0
  56. package/dist/operations/migration/symbol-coordinates-refresh.d.ts +3 -2
  57. package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -1
  58. package/dist/operations/migration/symbol-coordinates-refresh.js +4 -3
  59. package/dist/operations/migration/untrack-symbol-coordinates.d.ts +23 -0
  60. package/dist/operations/migration/untrack-symbol-coordinates.d.ts.map +1 -0
  61. package/dist/operations/migration/untrack-symbol-coordinates.js +67 -0
  62. package/dist/operations/modeling/requirement-modeler.d.ts.map +1 -1
  63. package/dist/operations/modeling/requirement-modeler.js +48 -1
  64. package/dist/operations/modeling/requirement-types.d.ts +2 -1
  65. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  66. package/dist/operations/modeling/requirement-utils.d.ts +1 -1
  67. package/dist/operations/modeling/requirement-utils.d.ts.map +1 -1
  68. package/dist/operations/modeling/requirement-utils.js +8 -1
  69. package/dist/operations/mutation/omitted-properties.d.ts +11 -0
  70. package/dist/operations/mutation/omitted-properties.d.ts.map +1 -0
  71. package/dist/operations/mutation/omitted-properties.js +76 -0
  72. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  73. package/dist/operations/mutation/serialization.js +1 -0
  74. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  75. package/dist/operations/mutation/source-authoring.js +39 -20
  76. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  77. package/dist/operations/mutation/strict-fact.js +6 -2
  78. package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
  79. package/dist/operations/mutation/symbol-granularity.js +5 -3
  80. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  81. package/dist/operations/mutation/upsert.js +7 -1
  82. package/dist/operations/mutation/validation.d.ts +2 -0
  83. package/dist/operations/mutation/validation.d.ts.map +1 -1
  84. package/dist/operations/mutation/validation.js +4 -0
  85. package/dist/operations/proof/claim-bindings.d.ts +41 -0
  86. package/dist/operations/proof/claim-bindings.d.ts.map +1 -0
  87. package/dist/operations/proof/claim-bindings.js +364 -0
  88. package/dist/operations/proof/code-certificates.d.ts +155 -0
  89. package/dist/operations/proof/code-certificates.d.ts.map +1 -0
  90. package/dist/operations/proof/code-certificates.js +894 -0
  91. package/dist/operations/proof/code-models.d.ts +96 -0
  92. package/dist/operations/proof/code-models.d.ts.map +1 -0
  93. package/dist/operations/proof/code-models.js +601 -0
  94. package/dist/operations/proof/code-runner.d.ts +40 -0
  95. package/dist/operations/proof/code-runner.d.ts.map +1 -0
  96. package/dist/operations/proof/code-runner.js +164 -0
  97. package/dist/operations/proof/code-scope.d.ts +14 -1
  98. package/dist/operations/proof/code-scope.d.ts.map +1 -1
  99. package/dist/operations/proof/code-scope.js +44 -2
  100. package/dist/operations/proof/compact-receipts.d.ts.map +1 -1
  101. package/dist/operations/proof/compact-receipts.js +4 -3
  102. package/dist/operations/proof/explain.d.ts +32 -0
  103. package/dist/operations/proof/explain.d.ts.map +1 -1
  104. package/dist/operations/proof/explain.js +91 -0
  105. package/dist/operations/proof/ingest-proof.d.ts +9 -1
  106. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  107. package/dist/operations/proof/ingest-proof.js +42 -2
  108. package/dist/operations/proof/receipt-compaction.d.ts +2 -0
  109. package/dist/operations/proof/receipt-compaction.d.ts.map +1 -1
  110. package/dist/operations/proof/receipt-compaction.js +6 -2
  111. package/dist/operations/proof/regular-language.d.ts +89 -0
  112. package/dist/operations/proof/regular-language.d.ts.map +1 -0
  113. package/dist/operations/proof/regular-language.js +943 -0
  114. package/dist/operations/proof/source-code-readers.d.ts +12 -0
  115. package/dist/operations/proof/source-code-readers.d.ts.map +1 -0
  116. package/dist/operations/proof/source-code-readers.js +699 -0
  117. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  118. package/dist/operations/semantic-advisor/analyze-prose.js +2 -1
  119. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  120. package/dist/operations/semantic-advisor/shared.js +4 -0
  121. package/dist/operations/semantic-advisor/strict-count-rules.d.ts.map +1 -1
  122. package/dist/operations/semantic-advisor/strict-count-rules.js +8 -2
  123. package/dist/operations/semantic-advisor/strict-rules.d.ts +8 -0
  124. package/dist/operations/semantic-advisor/strict-rules.d.ts.map +1 -1
  125. package/dist/operations/semantic-advisor/strict-rules.js +234 -4
  126. package/dist/operations/semantic-advisor/types.d.ts +3 -2
  127. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  128. package/dist/plugins/approved-source-analyzers.generated.d.ts +6 -6
  129. package/dist/plugins/approved-source-analyzers.generated.js +6 -6
  130. package/dist/prolog.d.ts.map +1 -1
  131. package/dist/prolog.js +13 -3
  132. package/dist/proof/command-test-report.d.ts +4 -3
  133. package/dist/proof/command-test-report.d.ts.map +1 -1
  134. package/dist/proof/command-test-report.js +4 -10
  135. package/dist/proof/evaluate.d.ts.map +1 -1
  136. package/dist/proof/evaluate.js +18 -2
  137. package/dist/proof/inspect.d.ts.map +1 -1
  138. package/dist/proof/inspect.js +58 -0
  139. package/dist/proof/producers/playwright-reporter.d.ts +3 -0
  140. package/dist/proof/producers/playwright-reporter.d.ts.map +1 -1
  141. package/dist/proof/producers/playwright-reporter.js +25 -2
  142. package/dist/proof/step-runner.d.ts +36 -0
  143. package/dist/proof/step-runner.d.ts.map +1 -0
  144. package/dist/proof/step-runner.js +234 -0
  145. package/dist/public/evidence-levels.d.ts +12 -0
  146. package/dist/public/evidence-levels.d.ts.map +1 -0
  147. package/dist/public/evidence-levels.js +19 -0
  148. package/dist/public/impact/coverage-depth-quality.d.ts +7 -0
  149. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  150. package/dist/public/impact/coverage-depth-quality.js +59 -0
  151. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  152. package/dist/public/impact/full-kb-quality.js +3 -1
  153. package/dist/public/operations/catalog.d.ts +315 -11
  154. package/dist/public/operations/catalog.d.ts.map +1 -1
  155. package/dist/public/operations/check-executor.d.ts +2 -0
  156. package/dist/public/operations/check-executor.d.ts.map +1 -1
  157. package/dist/public/operations/check-executor.js +6 -0
  158. package/dist/public/operations/evidence-floor.d.ts +48 -0
  159. package/dist/public/operations/evidence-floor.d.ts.map +1 -0
  160. package/dist/public/operations/evidence-floor.js +143 -0
  161. package/dist/public/operations/migration-plan.d.ts +19 -5
  162. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  163. package/dist/public/operations/migration-plan.js +22 -5
  164. package/dist/public/operations/node-ports.d.ts.map +1 -1
  165. package/dist/public/operations/node-ports.js +4 -1
  166. package/dist/public/operations/proof-contract-claims.d.ts +26 -0
  167. package/dist/public/operations/proof-contract-claims.d.ts.map +1 -0
  168. package/dist/public/operations/proof-contract-claims.js +71 -0
  169. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  170. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  171. package/dist/public/operations/specs/check-rules.generated.js +3 -1
  172. package/dist/public/operations/specs/check.d.ts +1 -1
  173. package/dist/public/operations/specs/composite.d.ts +16 -3
  174. package/dist/public/operations/specs/composite.d.ts.map +1 -1
  175. package/dist/public/operations/specs/modeling.d.ts +16 -3
  176. package/dist/public/operations/specs/modeling.d.ts.map +1 -1
  177. package/dist/public/operations/specs/modeling.js +22 -3
  178. package/dist/public/operations/specs/mutation-schemas.d.ts +289 -7
  179. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  180. package/dist/public/operations/specs/mutation-schemas.js +28 -4
  181. package/dist/public/operations/specs/proof.d.ts +9 -0
  182. package/dist/public/operations/specs/proof.d.ts.map +1 -1
  183. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  184. package/dist/public/operations/specs/reporting.js +19 -10
  185. package/dist/public/operations/specs/upsert.d.ts +289 -7
  186. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  187. package/dist/public/proof-certificates.d.ts +163 -0
  188. package/dist/public/proof-certificates.d.ts.map +1 -0
  189. package/dist/public/proof-certificates.js +419 -0
  190. package/dist/public/proof-protocol.d.ts +259 -3
  191. package/dist/public/proof-protocol.d.ts.map +1 -1
  192. package/dist/public/proof-protocol.js +524 -3
  193. package/dist/public/proof-receipt.d.ts +90 -6
  194. package/dist/public/proof-receipt.d.ts.map +1 -1
  195. package/dist/public/proof-receipt.js +88 -7
  196. package/dist/public/proof-sources.d.ts +102 -0
  197. package/dist/public/proof-sources.d.ts.map +1 -0
  198. package/dist/public/proof-sources.js +1292 -0
  199. package/dist/public/schemas/entity.d.ts.map +1 -1
  200. package/dist/public/schemas/entity.js +39 -2
  201. package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
  202. package/dist/public/skills/kibi-commit-review/SKILL.md +8 -8
  203. package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
  204. package/dist/public/skills/kibi-decisions/SKILL.md +13 -2
  205. package/dist/public/skills/kibi-decisions/resources/decision-records.md +9 -1
  206. package/dist/public/skills/kibi-freshness/SKILL.md +5 -1
  207. package/dist/public/skills/kibi-traceability/SKILL.md +12 -1
  208. package/dist/public/skills/kibi-usage/SKILL.md +8 -4
  209. package/dist/public/skills/kibi-usage/resources/proof.md +221 -20
  210. package/dist/public/skills/kibi-usage/resources/workflows.md +4 -4
  211. package/dist/report/html-report.css +83 -11
  212. package/dist/report/html-report.d.ts +8 -0
  213. package/dist/report/html-report.d.ts.map +1 -1
  214. package/dist/report/html-report.js +214 -22
  215. package/dist/report/report-view.d.ts +2 -2
  216. package/dist/report/report-view.d.ts.map +1 -1
  217. package/dist/report/report-view.js +8 -0
  218. package/dist/schemas/entity.schema.json +404 -5
  219. package/dist/search-answer.d.ts.map +1 -1
  220. package/dist/search-answer.js +1 -0
  221. package/dist/signoff/html.d.ts.map +1 -1
  222. package/dist/signoff/html.js +3 -0
  223. package/dist/signoff/markdown.d.ts.map +1 -1
  224. package/dist/signoff/markdown.js +3 -0
  225. package/dist/signoff/model.d.ts +20 -0
  226. package/dist/signoff/model.d.ts.map +1 -1
  227. package/dist/signoff/model.js +20 -1
  228. package/dist/traceability/evidence-model.d.ts +13 -12
  229. package/dist/traceability/evidence-model.d.ts.map +1 -1
  230. package/dist/traceability/evidence-model.js +1 -1
  231. package/dist/traceability/impact-knowledge.d.ts.map +1 -1
  232. package/dist/traceability/impact-knowledge.js +1 -0
  233. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  234. package/dist/traceability/staged-diagnostics.js +24 -20
  235. package/dist/traceability/staged-impact-contract.d.ts +4 -4
  236. package/dist/traceability/staged-impact-contract.js +9 -9
  237. package/dist/traceability/staged-symbols-manifest.d.ts +11 -3
  238. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  239. package/dist/traceability/staged-symbols-manifest.js +47 -140
  240. package/dist/traceability/temp-kb.d.ts.map +1 -1
  241. package/dist/traceability/temp-kb.js +1 -0
  242. package/dist/types/entities.d.ts +5 -2
  243. package/dist/types/entities.d.ts.map +1 -1
  244. package/dist/utils/merge-driver-setup.d.ts +8 -1
  245. package/dist/utils/merge-driver-setup.d.ts.map +1 -1
  246. package/dist/utils/merge-driver-setup.js +23 -2
  247. package/dist/utils/rule-registry.generated.d.ts +10 -0
  248. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  249. package/dist/utils/rule-registry.generated.js +12 -0
  250. package/dist/utils/schema-version.d.ts +1 -1
  251. package/dist/utils/schema-version.d.ts.map +1 -1
  252. package/dist/utils/schema-version.js +7 -1
  253. package/dist/utils/strict-modeling.d.ts +3 -2
  254. package/dist/utils/strict-modeling.d.ts.map +1 -1
  255. package/dist/utils/strict-modeling.js +37 -4
  256. package/dist/workspace-cwd.d.ts +15 -0
  257. package/dist/workspace-cwd.d.ts.map +1 -0
  258. package/dist/workspace-cwd.js +86 -0
  259. package/package.json +6 -2
  260. package/schema/entities.pl +6 -0
  261. package/schema/validation.pl +7 -2
  262. package/src/public/evidence-levels.ts +23 -0
  263. package/src/public/impact/coverage-depth-quality.ts +73 -0
  264. package/src/public/impact/full-kb-quality.ts +6 -1
  265. package/src/public/operations/check-executor.ts +13 -0
  266. package/src/public/operations/evidence-floor.ts +203 -0
  267. package/src/public/operations/migration-plan.ts +39 -6
  268. package/src/public/operations/node-ports.ts +3 -1
  269. package/src/public/operations/proof-contract-claims.ts +103 -0
  270. package/src/public/operations/specs/check-rules.generated.ts +3 -1
  271. package/src/public/operations/specs/modeling.ts +24 -5
  272. package/src/public/operations/specs/mutation-schemas.ts +30 -4
  273. package/src/public/operations/specs/reporting.ts +20 -10
  274. package/src/public/proof-certificates.ts +566 -0
  275. package/src/public/proof-protocol-types.d.ts +69 -1
  276. package/src/public/proof-protocol.ts +694 -3
  277. package/src/public/proof-receipt.ts +133 -7
  278. package/src/public/proof-sources.ts +1656 -0
  279. package/src/public/schemas/entity.ts +51 -3
  280. package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
  281. package/src/public/skills/kibi-commit-review/SKILL.md +8 -8
  282. package/src/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
  283. package/src/public/skills/kibi-decisions/SKILL.md +13 -2
  284. package/src/public/skills/kibi-decisions/resources/decision-records.md +9 -1
  285. package/src/public/skills/kibi-freshness/SKILL.md +5 -1
  286. package/src/public/skills/kibi-traceability/SKILL.md +12 -1
  287. package/src/public/skills/kibi-usage/SKILL.md +8 -4
  288. package/src/public/skills/kibi-usage/resources/proof.md +221 -20
  289. package/src/public/skills/kibi-usage/resources/workflows.md +4 -4
  290. package/src/schemas/entity.schema.json +404 -5
  291. package/dist/commands/proof-impact.d.ts +0 -7
  292. package/dist/commands/proof-impact.d.ts.map +0 -1
  293. package/dist/commands/proof-impact.js +0 -39
  294. package/dist/operations/proof/baseline-diff.d.ts +0 -41
  295. package/dist/operations/proof/baseline-diff.d.ts.map +0 -1
  296. package/dist/operations/proof/baseline-diff.js +0 -153
  297. package/dist/operations/proof/impact.d.ts +0 -51
  298. package/dist/operations/proof/impact.d.ts.map +0 -1
  299. package/dist/operations/proof/impact.js +0 -138
  300. package/dist/traceability/reviewed-coordinate-refresh.d.ts +0 -10
  301. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +0 -1
  302. package/dist/traceability/reviewed-coordinate-refresh.js +0 -49
@@ -0,0 +1,943 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ const MAX_UNIT = 0xffff;
19
+ const MAX_CODE_POINT = 0x10ffff;
20
+ const MAX_NFA_STATES = 20_000;
21
+ const MAX_PRODUCT_STATES = 50_000;
22
+ /** A pattern Kibi refuses to model; the message says why. */
23
+ export class UnsupportedPattern extends Error {
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Code point sets
27
+ function normalize(ranges) {
28
+ const sorted = [...ranges]
29
+ .filter(([lo, hi]) => lo <= hi)
30
+ .sort((a, b) => a[0] - b[0]);
31
+ const out = [];
32
+ for (const [lo, hi] of sorted) {
33
+ const last = out[out.length - 1];
34
+ if (last !== undefined && lo <= last[1] + 1)
35
+ out[out.length - 1] = [last[0], Math.max(last[1], hi)];
36
+ else
37
+ out.push([lo, hi]);
38
+ }
39
+ return out;
40
+ }
41
+ function complement(ranges, max) {
42
+ const out = [];
43
+ let next = 0;
44
+ for (const [lo, hi] of normalize(ranges)) {
45
+ if (lo > next)
46
+ out.push([next, lo - 1]);
47
+ next = hi + 1;
48
+ }
49
+ if (next <= max)
50
+ out.push([next, max]);
51
+ return out;
52
+ }
53
+ function single(code) {
54
+ return [[code, code]];
55
+ }
56
+ const DIGIT = [[0x30, 0x39]];
57
+ const WORD = normalize([
58
+ [0x30, 0x39],
59
+ [0x41, 0x5a],
60
+ [0x5f, 0x5f],
61
+ [0x61, 0x7a],
62
+ ]);
63
+ const JS_SPACE = normalize([
64
+ [0x09, 0x0d],
65
+ [0x20, 0x20],
66
+ [0xa0, 0xa0],
67
+ [0x1680, 0x1680],
68
+ [0x2000, 0x200a],
69
+ [0x2028, 0x2029],
70
+ [0x202f, 0x202f],
71
+ [0x205f, 0x205f],
72
+ [0x3000, 0x3000],
73
+ [0xfeff, 0xfeff],
74
+ ]);
75
+ const PYTHON_ASCII_SPACE = normalize([
76
+ [0x09, 0x0d],
77
+ [0x20, 0x20],
78
+ ]);
79
+ // str.isspace(): bidirectional types WS, B and S, and category Zs.
80
+ const PYTHON_UNICODE_SPACE = normalize([
81
+ [0x09, 0x0d],
82
+ [0x1c, 0x20],
83
+ [0x85, 0x85],
84
+ [0xa0, 0xa0],
85
+ [0x1680, 0x1680],
86
+ [0x2000, 0x200a],
87
+ [0x2028, 0x2029],
88
+ [0x202f, 0x202f],
89
+ [0x205f, 0x205f],
90
+ [0x3000, 0x3000],
91
+ ]);
92
+ const JS_LINE_TERMINATORS = normalize([
93
+ [0x0a, 0x0a],
94
+ [0x0d, 0x0d],
95
+ [0x2028, 0x2029],
96
+ ]);
97
+ /**
98
+ * The characters equal to an ASCII letter under case-insensitive matching,
99
+ * beyond its ASCII partner: JavaScript with the u flag folds U+017F to s
100
+ * and U+212A to k (simple case folding); Python's Unicode IGNORECASE also
101
+ * treats U+0130 and U+0131 as i (its documented four extra letters).
102
+ */
103
+ function caseExtras(flavor, unicode, ascii) {
104
+ const extras = new Map();
105
+ if (!unicode || ascii)
106
+ return extras;
107
+ const add = (letter, ...codes) => {
108
+ for (const variant of [letter.toLowerCase(), letter.toUpperCase()])
109
+ extras.set(variant.charCodeAt(0), codes);
110
+ };
111
+ add("s", 0x017f);
112
+ add("k", 0x212a);
113
+ if (flavor === "python")
114
+ add("i", 0x0130, 0x0131);
115
+ return extras;
116
+ }
117
+ /**
118
+ * A character with no case variants: no other character upper- or
119
+ * lower-cases to it, so every engine's case folding leaves it alone.
120
+ */
121
+ function caseless(code) {
122
+ const char = String.fromCodePoint(code);
123
+ return char.toUpperCase() === char && char.toLowerCase() === char;
124
+ }
125
+ function caseClosed(ranges, syntax) {
126
+ if (!syntax.ignoreCase)
127
+ return ranges;
128
+ const extras = caseExtras(syntax.flavor, syntax.unicode, syntax.ascii);
129
+ const added = [];
130
+ for (const [lo, hi] of ranges) {
131
+ for (let code = Math.max(lo, 0x41); code <= Math.min(hi, 0x7a); code++) {
132
+ if (code >= 0x41 && code <= 0x5a)
133
+ added.push([code + 32, code + 32]);
134
+ else if (code >= 0x61 && code <= 0x7a)
135
+ added.push([code - 32, code - 32]);
136
+ for (const extra of extras.get(code) ?? [])
137
+ added.push([extra, extra]);
138
+ }
139
+ for (const [letter, codes] of extras)
140
+ for (const extra of codes)
141
+ if (extra >= lo && extra <= hi) {
142
+ added.push([letter, letter]);
143
+ for (const other of codes)
144
+ added.push([other, other]);
145
+ }
146
+ }
147
+ return normalize([...ranges, ...added]);
148
+ }
149
+ function syntaxOf(spec) {
150
+ const flags = spec.flags ?? "";
151
+ const allowed = spec.flavor === "js" ? "disu" : "iasu";
152
+ for (const flag of flags) {
153
+ if (!allowed.includes(flag))
154
+ throw new UnsupportedPattern(spec.flavor === "js"
155
+ ? `the ${flag} flag is not supported (g and y make test() depend on lastIndex, m makes ^ and $ match at every line, v changes class syntax)`
156
+ : `the Python flag ${flag} is not supported (MULTILINE, VERBOSE and LOCALE change what the pattern means)`);
157
+ }
158
+ return {
159
+ flavor: spec.flavor,
160
+ unicode: spec.flavor === "python" || flags.includes("u"),
161
+ ignoreCase: flags.includes("i"),
162
+ dotAll: flags.includes("s"),
163
+ ascii: spec.flavor === "python" && flags.includes("a"),
164
+ };
165
+ }
166
+ class Parser {
167
+ source;
168
+ syntax;
169
+ index = 0;
170
+ constructor(source, syntax) {
171
+ this.source = source;
172
+ this.syntax = syntax;
173
+ }
174
+ parse() {
175
+ const node = this.alternation();
176
+ if (this.index < this.source.length)
177
+ throw new UnsupportedPattern(`unexpected ${JSON.stringify(this.source[this.index])} at offset ${this.index}`);
178
+ return node;
179
+ }
180
+ peek(offset = 0) {
181
+ return this.source[this.index + offset];
182
+ }
183
+ fail(message) {
184
+ throw new UnsupportedPattern(`${message} (at offset ${this.index})`);
185
+ }
186
+ alternation() {
187
+ const items = [this.sequence()];
188
+ while (this.peek() === "|") {
189
+ this.index++;
190
+ items.push(this.sequence());
191
+ }
192
+ return items.length === 1 ? items[0] : { type: "alt", items };
193
+ }
194
+ sequence() {
195
+ const items = [];
196
+ while (this.index < this.source.length) {
197
+ const char = this.peek();
198
+ if (char === "|" || char === ")")
199
+ break;
200
+ const atom = this.atom();
201
+ items.push(this.quantified(atom));
202
+ }
203
+ return { type: "seq", items };
204
+ }
205
+ /** A counted quantifier at `index`: its bounds and length, or null. */
206
+ counted() {
207
+ const rest = this.source.slice(this.index);
208
+ const match = this.syntax.flavor === "python"
209
+ ? /^\{(\d*)(?:(,)(\d*))?\}/.exec(rest)
210
+ : /^\{(\d+)(?:(,)(\d*))?\}/.exec(rest);
211
+ if (match === null || (match[1] === "" && match[2] === undefined))
212
+ return null;
213
+ if (match[1] === "" && match[3] === "")
214
+ this.fail("a {,} quantifier");
215
+ const min = match[1] === "" ? 0 : Number(match[1]);
216
+ const max = match[2] === undefined
217
+ ? min
218
+ : match[3] === ""
219
+ ? Number.POSITIVE_INFINITY
220
+ : Number(match[3]);
221
+ return { min, max, length: match[0].length };
222
+ }
223
+ quantified(atom) {
224
+ const char = this.peek();
225
+ let min;
226
+ let max;
227
+ if (char === "*")
228
+ [min, max] = [0, Number.POSITIVE_INFINITY];
229
+ else if (char === "+")
230
+ [min, max] = [1, Number.POSITIVE_INFINITY];
231
+ else if (char === "?")
232
+ [min, max] = [0, 1];
233
+ else if (char === "{") {
234
+ const bounds = this.counted();
235
+ if (bounds === null) {
236
+ if (this.syntax.flavor === "js" && this.syntax.unicode)
237
+ this.fail("a { that does not start a quantifier");
238
+ return atom;
239
+ }
240
+ ({ min, max } = bounds);
241
+ if (max < min)
242
+ this.fail("a quantifier whose maximum is below its minimum");
243
+ if (min > 1000 || (Number.isFinite(max) && max > 1000))
244
+ this.fail("a counted repeat above 1000");
245
+ this.index += bounds.length - 1;
246
+ }
247
+ else
248
+ return atom;
249
+ if (atom.type === "assert")
250
+ this.fail("a quantified assertion");
251
+ this.index++;
252
+ const lazy = this.peek();
253
+ if (lazy === "?")
254
+ this.index++;
255
+ else if (lazy === "+" && this.syntax.flavor === "python")
256
+ this.fail("a possessive quantifier");
257
+ const after = this.peek();
258
+ if (after === "*" ||
259
+ after === "+" ||
260
+ after === "?" ||
261
+ (after === "{" && this.counted() !== null))
262
+ this.fail("a quantifier on a quantifier");
263
+ return { type: "repeat", item: atom, min, max };
264
+ }
265
+ atom() {
266
+ const char = this.peek();
267
+ if (char === "(")
268
+ return this.group();
269
+ if (char === "[")
270
+ return { type: "set", ranges: this.characterClass() };
271
+ if (char === ".") {
272
+ this.index++;
273
+ const excluded = this.syntax.dotAll
274
+ ? []
275
+ : this.syntax.flavor === "js"
276
+ ? JS_LINE_TERMINATORS
277
+ : single(0x0a);
278
+ return { type: "set", ranges: complement(excluded, this.maxChar()) };
279
+ }
280
+ if (char === "^") {
281
+ this.index++;
282
+ return { type: "assert", kind: "bol" };
283
+ }
284
+ if (char === "$") {
285
+ this.index++;
286
+ return {
287
+ type: "assert",
288
+ kind: this.syntax.flavor === "python" ? "eolnl" : "eol",
289
+ };
290
+ }
291
+ if (char === "\\")
292
+ return this.escapeAtom();
293
+ if (char === "*" || char === "+" || char === "?")
294
+ this.fail("a quantifier with nothing to repeat");
295
+ if (char === "{" && this.counted() !== null)
296
+ this.fail("a quantifier with nothing to repeat");
297
+ if (char === "]" && this.syntax.flavor === "js" && this.syntax.unicode)
298
+ this.fail("a lone ]");
299
+ if (char === "}" && this.syntax.flavor === "js" && this.syntax.unicode)
300
+ this.fail("a lone }");
301
+ const code = this.readLiteral();
302
+ return { type: "set", ranges: this.literalSet(code) };
303
+ }
304
+ maxChar() {
305
+ return this.syntax.unicode ? MAX_CODE_POINT : MAX_UNIT;
306
+ }
307
+ readLiteral() {
308
+ const code = this.syntax.unicode
309
+ ? this.source.codePointAt(this.index)
310
+ : this.source.charCodeAt(this.index);
311
+ this.index += code > 0xffff ? 2 : 1;
312
+ return code;
313
+ }
314
+ literalSet(code) {
315
+ if (this.syntax.ignoreCase && code > 0x7f && !caseless(code))
316
+ this.fail("a non-ASCII letter under case-insensitive matching (Kibi folds case exactly only for ASCII letters)");
317
+ return caseClosed(single(code), this.syntax);
318
+ }
319
+ group() {
320
+ this.index++;
321
+ if (this.peek() === "?") {
322
+ const rest = this.source.slice(this.index);
323
+ if (rest.startsWith("?:"))
324
+ this.index += 2;
325
+ else if (/^\?P?<[A-Za-z_][A-Za-z0-9_]*>/.test(rest)) {
326
+ if (rest.startsWith("?P") && this.syntax.flavor !== "python")
327
+ this.fail("a (?P<name>) group outside Python");
328
+ if (rest.startsWith("?<") && this.syntax.flavor === "python")
329
+ this.fail("a (?<name>) group in Python");
330
+ this.index += rest.indexOf(">") + 1;
331
+ }
332
+ else if (/^\?<?[=!]/.test(rest))
333
+ this.fail("lookaround (Kibi decides only regular languages)");
334
+ else if (rest.startsWith("?P=") || rest.startsWith("?("))
335
+ this.fail("a backreference or conditional group");
336
+ else
337
+ this.fail("an inline flag, comment or atomic group");
338
+ }
339
+ const inner = this.alternation();
340
+ if (this.peek() !== ")")
341
+ this.fail("an unclosed group");
342
+ this.index++;
343
+ return inner;
344
+ }
345
+ escapeAtom() {
346
+ const next = this.peek(1);
347
+ if (next === undefined)
348
+ this.fail("a trailing backslash");
349
+ if (next === "b" || next === "B")
350
+ this.fail("a word boundary (Kibi decides only regular languages)");
351
+ if (/[1-9]/.test(next) || next === "k")
352
+ this.fail("a backreference (Kibi decides only regular languages)");
353
+ if (this.syntax.flavor === "python" && (next === "A" || next === "Z")) {
354
+ this.index += 2;
355
+ return { type: "assert", kind: next === "A" ? "bol" : "eol" };
356
+ }
357
+ const escaped = this.escape(false);
358
+ return {
359
+ type: "set",
360
+ ranges: "code" in escaped ? this.literalSet(escaped.code) : escaped.ranges,
361
+ };
362
+ }
363
+ /**
364
+ * One escape after the backslash at `index`, advancing past it: a single
365
+ * character (its code, not yet case-folded) or a class escape's set.
366
+ */
367
+ escape(inClass) {
368
+ this.index++;
369
+ const char = this.peek();
370
+ this.index++;
371
+ const code = (value) => ({ code: value });
372
+ switch (char) {
373
+ case "d":
374
+ case "D":
375
+ case "w":
376
+ case "W":
377
+ case "s":
378
+ case "S":
379
+ return { ranges: this.classEscape(char) };
380
+ case "t":
381
+ return code(0x09);
382
+ case "n":
383
+ return code(0x0a);
384
+ case "r":
385
+ return code(0x0d);
386
+ case "v":
387
+ return code(0x0b);
388
+ case "f":
389
+ return code(0x0c);
390
+ case "a":
391
+ if (this.syntax.flavor === "python")
392
+ return code(0x07);
393
+ break;
394
+ case "b":
395
+ if (inClass)
396
+ return code(0x08);
397
+ break;
398
+ case "0":
399
+ if (!/[0-9]/.test(this.peek() ?? ""))
400
+ return code(0);
401
+ this.fail("an octal escape");
402
+ break;
403
+ case "x": {
404
+ const hex = /^[0-9A-Fa-f]{2}/.exec(this.source.slice(this.index));
405
+ if (hex === null)
406
+ this.fail("a malformed \\x escape");
407
+ this.index += 2;
408
+ return code(Number.parseInt(hex[0], 16));
409
+ }
410
+ case "u": {
411
+ const rest = this.source.slice(this.index);
412
+ const braced = /^\{([0-9A-Fa-f]{1,6})\}/.exec(rest);
413
+ if (braced !== null && this.syntax.flavor === "js" && this.syntax.unicode) {
414
+ this.index += braced[0].length;
415
+ const value = Number.parseInt(braced[1], 16);
416
+ if (value > MAX_CODE_POINT)
417
+ this.fail("a code point above U+10FFFF");
418
+ return code(value);
419
+ }
420
+ const hex = /^[0-9A-Fa-f]{4}/.exec(rest);
421
+ if (hex === null)
422
+ this.fail("a malformed \\u escape");
423
+ this.index += 4;
424
+ let value = Number.parseInt(hex[0], 16);
425
+ // In u mode a \u escape pair spells one astral code point.
426
+ const low = /^\\u(D[C-F][0-9A-F]{2})/i.exec(this.source.slice(this.index));
427
+ if (this.syntax.flavor === "js" &&
428
+ this.syntax.unicode &&
429
+ value >= 0xd800 &&
430
+ value <= 0xdbff &&
431
+ low !== null) {
432
+ this.index += 6;
433
+ value =
434
+ 0x10000 +
435
+ ((value - 0xd800) << 10) +
436
+ (Number.parseInt(low[1], 16) - 0xdc00);
437
+ }
438
+ return code(value);
439
+ }
440
+ case "U":
441
+ if (this.syntax.flavor === "python") {
442
+ const hex = /^[0-9A-Fa-f]{8}/.exec(this.source.slice(this.index));
443
+ if (hex === null)
444
+ this.fail("a malformed \\U escape");
445
+ this.index += 8;
446
+ const value = Number.parseInt(hex[0], 16);
447
+ if (value > MAX_CODE_POINT)
448
+ this.fail("a code point above U+10FFFF");
449
+ return code(value);
450
+ }
451
+ break;
452
+ case "c":
453
+ if (this.syntax.flavor === "js") {
454
+ const letter = this.peek() ?? "";
455
+ if (/[A-Za-z]/.test(letter)) {
456
+ this.index++;
457
+ return code(letter.charCodeAt(0) % 32);
458
+ }
459
+ }
460
+ break;
461
+ case "p":
462
+ case "P":
463
+ this.fail("a Unicode property escape");
464
+ break;
465
+ case "N":
466
+ if (this.syntax.flavor === "python")
467
+ this.fail("a \\N{name} escape");
468
+ break;
469
+ default:
470
+ break;
471
+ }
472
+ // An identity escape: a syntax character stands for itself (in Python
473
+ // any ASCII punctuation does); any other escape is one Kibi does not
474
+ // model.
475
+ if (/[\^$\\.*+?()[\]{}|/\-]/.test(char))
476
+ return code(char.charCodeAt(0));
477
+ if (this.syntax.flavor === "python" &&
478
+ /[^A-Za-z0-9]/.test(char) &&
479
+ char.charCodeAt(0) < 0x80)
480
+ return code(char.charCodeAt(0));
481
+ this.index--;
482
+ return this.fail(`the escape \\${char}`);
483
+ }
484
+ classEscape(char) {
485
+ const lower = char.toLowerCase();
486
+ let base;
487
+ if (this.syntax.flavor === "python" && !this.syntax.ascii) {
488
+ if (lower === "s")
489
+ base = PYTHON_UNICODE_SPACE;
490
+ else
491
+ this.fail(`\\${char} in a Python str pattern matches any Unicode ${lower === "d" ? "digit" : "word character"}; use an explicit class or re.ASCII`);
492
+ }
493
+ else if (lower === "d")
494
+ base = DIGIT;
495
+ else if (lower === "w")
496
+ base = WORD;
497
+ else
498
+ base = this.syntax.flavor === "python" ? PYTHON_ASCII_SPACE : JS_SPACE;
499
+ const closed = caseClosed(base, this.syntax);
500
+ return char === lower ? closed : complement(closed, this.maxChar());
501
+ }
502
+ characterClass() {
503
+ this.index++;
504
+ let negated = false;
505
+ if (this.peek() === "^") {
506
+ negated = true;
507
+ this.index++;
508
+ }
509
+ const ranges = [];
510
+ let first = true;
511
+ for (;;) {
512
+ const char = this.peek();
513
+ if (char === undefined)
514
+ this.fail("an unclosed character class");
515
+ if (char === "]" && !(first && this.syntax.flavor === "python")) {
516
+ this.index++;
517
+ break;
518
+ }
519
+ first = false;
520
+ if (char === "[" &&
521
+ this.syntax.flavor === "python" &&
522
+ /^\[[:=.]/.test(this.source.slice(this.index)))
523
+ this.fail("a POSIX class");
524
+ const start = this.classAtom();
525
+ if (this.peek() === "-" &&
526
+ this.peek(1) !== undefined &&
527
+ this.peek(1) !== "]") {
528
+ this.index++;
529
+ const end = this.classAtom();
530
+ if (start.single === null || end.single === null) {
531
+ if (this.syntax.unicode)
532
+ this.fail("a class escape as a range endpoint");
533
+ // Without u, JavaScript reads [\d-x] as \d, - and x.
534
+ ranges.push(...start.ranges, ...this.literalSet(0x2d), ...end.ranges);
535
+ continue;
536
+ }
537
+ if (end.single < start.single)
538
+ this.fail("a range out of order");
539
+ if (this.syntax.ignoreCase && end.single > 0x7f)
540
+ this.fail("a non-ASCII range under case-insensitive matching (Kibi folds case exactly only for ASCII letters)");
541
+ ranges.push(...caseClosed([[start.single, end.single]], this.syntax));
542
+ }
543
+ else
544
+ ranges.push(...start.ranges);
545
+ }
546
+ const set = normalize(ranges);
547
+ return negated ? complement(set, this.maxChar()) : set;
548
+ }
549
+ classAtom() {
550
+ if (this.peek() === "\\") {
551
+ if (this.peek(1) === "B")
552
+ this.fail("\\B inside a class");
553
+ const escaped = this.escape(true);
554
+ return "code" in escaped
555
+ ? { ranges: this.literalSet(escaped.code), single: escaped.code }
556
+ : { ranges: escaped.ranges, single: null };
557
+ }
558
+ const value = this.readLiteral();
559
+ return { ranges: this.literalSet(value), single: value };
560
+ }
561
+ }
562
+ class Nfa {
563
+ edges = [];
564
+ state() {
565
+ if (this.edges.length >= MAX_NFA_STATES)
566
+ throw new UnsupportedPattern(`the pattern needs more than ${MAX_NFA_STATES} automaton states`);
567
+ this.edges.push([]);
568
+ return this.edges.length - 1;
569
+ }
570
+ add(from, edge) {
571
+ this.edges[from].push(edge);
572
+ }
573
+ }
574
+ /** Code point ranges as UTF-16 code unit paths from `from` to `to`. */
575
+ function addCodePoints(nfa, from, to, ranges, unicode) {
576
+ if (!unicode) {
577
+ const units = normalize(ranges.map(([lo, hi]) => [lo, Math.min(hi, MAX_UNIT)]));
578
+ if (units.length > 0)
579
+ nfa.add(from, { type: "units", ranges: units, to });
580
+ return;
581
+ }
582
+ // Lone surrogates never occur in well-formed strings, so a code point set
583
+ // keeps only scalar values: BMP ranges as one unit, astral ones as pairs.
584
+ const bmp = [];
585
+ const astral = [];
586
+ for (const [lo, hi] of ranges) {
587
+ if (lo <= 0xd7ff)
588
+ bmp.push([lo, Math.min(hi, 0xd7ff)]);
589
+ if (hi >= 0xe000 && lo <= 0xffff)
590
+ bmp.push([Math.max(lo, 0xe000), Math.min(hi, 0xffff)]);
591
+ if (hi >= 0x10000)
592
+ astral.push([Math.max(lo, 0x10000), hi]);
593
+ }
594
+ const units = normalize(bmp);
595
+ if (units.length > 0)
596
+ nfa.add(from, { type: "units", ranges: units, to });
597
+ for (const [lo, hi] of normalize(astral)) {
598
+ const highOf = (code) => 0xd800 + ((code - 0x10000) >> 10);
599
+ const lowOf = (code) => 0xdc00 + ((code - 0x10000) & 0x3ff);
600
+ const pair = (highLo, highHi, lowLo, lowHi) => {
601
+ const mid = nfa.state();
602
+ nfa.add(from, { type: "units", ranges: [[highLo, highHi]], to: mid });
603
+ nfa.add(mid, { type: "units", ranges: [[lowLo, lowHi]], to });
604
+ };
605
+ const firstHigh = highOf(lo);
606
+ const lastHigh = highOf(hi);
607
+ if (firstHigh === lastHigh) {
608
+ pair(firstHigh, firstHigh, lowOf(lo), lowOf(hi));
609
+ continue;
610
+ }
611
+ pair(firstHigh, firstHigh, lowOf(lo), 0xdfff);
612
+ if (lastHigh - firstHigh > 1)
613
+ pair(firstHigh + 1, lastHigh - 1, 0xdc00, 0xdfff);
614
+ pair(lastHigh, lastHigh, 0xdc00, lowOf(hi));
615
+ }
616
+ }
617
+ function build(nfa, node, from, to, unicode) {
618
+ switch (node.type) {
619
+ case "set":
620
+ addCodePoints(nfa, from, to, node.ranges, unicode);
621
+ return;
622
+ case "assert":
623
+ nfa.add(from, { type: "assert", kind: node.kind, to });
624
+ return;
625
+ case "seq": {
626
+ let current = from;
627
+ node.items.forEach((item, index) => {
628
+ const next = index === node.items.length - 1 ? to : nfa.state();
629
+ build(nfa, item, current, next, unicode);
630
+ current = next;
631
+ });
632
+ if (node.items.length === 0)
633
+ nfa.add(from, { type: "eps", to });
634
+ return;
635
+ }
636
+ case "alt":
637
+ for (const item of node.items)
638
+ build(nfa, item, from, to, unicode);
639
+ return;
640
+ case "repeat": {
641
+ let current = from;
642
+ for (let count = 0; count < node.min; count++) {
643
+ const next = nfa.state();
644
+ build(nfa, node.item, current, next, unicode);
645
+ current = next;
646
+ }
647
+ if (node.max === Number.POSITIVE_INFINITY) {
648
+ const loop = nfa.state();
649
+ nfa.add(current, { type: "eps", to: loop });
650
+ const back = nfa.state();
651
+ build(nfa, node.item, loop, back, unicode);
652
+ nfa.add(back, { type: "eps", to: loop });
653
+ nfa.add(loop, { type: "eps", to });
654
+ return;
655
+ }
656
+ for (let count = node.min; count < node.max; count++) {
657
+ const next = nfa.state();
658
+ nfa.add(current, { type: "eps", to });
659
+ build(nfa, node.item, current, next, unicode);
660
+ current = next;
661
+ }
662
+ nfa.add(current, { type: "eps", to });
663
+ return;
664
+ }
665
+ }
666
+ }
667
+ /** Compile a language to an NFA over UTF-16 code units, or refuse it. */
668
+ // implements REQ-kibi-non-numeric-proof-kinds
669
+ export function compileLanguage(spec) {
670
+ const nfa = new Nfa();
671
+ const start = nfa.state();
672
+ const accept = nfa.state();
673
+ if (spec.type === "literal") {
674
+ let current = start;
675
+ for (let index = 0; index < spec.value.length; index++) {
676
+ const next = index === spec.value.length - 1 ? accept : nfa.state();
677
+ const unit = spec.value.charCodeAt(index);
678
+ nfa.add(current, { type: "units", ranges: [[unit, unit]], to: next });
679
+ current = next;
680
+ }
681
+ if (spec.value.length === 0)
682
+ nfa.add(start, { type: "eps", to: accept });
683
+ return { nfa, start, accept, mode: "full" };
684
+ }
685
+ const syntax = syntaxOf(spec);
686
+ const tree = new Parser(spec.source, syntax).parse();
687
+ build(nfa, tree, start, accept, syntax.unicode);
688
+ return { nfa, start, accept, mode: spec.mode };
689
+ }
690
+ const FREE = 0;
691
+ const NEWLINE_OR_END = 1;
692
+ const END = 2;
693
+ const ACCEPTED = "*";
694
+ class Determinizer {
695
+ compiled;
696
+ ids = new Map();
697
+ keys = [];
698
+ sets = [];
699
+ flags = [];
700
+ moves = new Map();
701
+ constructor(compiled) {
702
+ this.compiled = compiled;
703
+ }
704
+ initial() {
705
+ return this.intern(this.closure([this.config(this.compiled.start, FREE)], true), false, false);
706
+ }
707
+ config(state, constraint) {
708
+ return state * 3 + constraint;
709
+ }
710
+ closure(seed, atStart) {
711
+ const seen = new Set(seed);
712
+ const stack = [...seed];
713
+ while (stack.length > 0) {
714
+ const config = stack.pop();
715
+ const state = Math.floor(config / 3);
716
+ const constraint = config % 3;
717
+ for (const edge of this.compiled.nfa.edges[state] ?? []) {
718
+ let next = null;
719
+ if (edge.type === "eps")
720
+ next = this.config(edge.to, constraint);
721
+ else if (edge.type === "assert") {
722
+ if (edge.kind === "bol")
723
+ next = atStart ? this.config(edge.to, constraint) : null;
724
+ else if (edge.kind === "eol")
725
+ next = this.config(edge.to, END);
726
+ else
727
+ next = this.config(edge.to, Math.max(constraint, NEWLINE_OR_END));
728
+ }
729
+ if (next !== null && !seen.has(next)) {
730
+ seen.add(next);
731
+ stack.push(next);
732
+ }
733
+ }
734
+ }
735
+ return [...seen].sort((a, b) => a - b);
736
+ }
737
+ /**
738
+ * Intern a closed configuration set. Reaching the accept state settles the
739
+ * outcome: in search mode an unconstrained match accepts every extension;
740
+ * a constrained one (or any full match) accepts only when the rest of the
741
+ * input is what the constraint allows.
742
+ */
743
+ intern(configs, atEnd, atNewline) {
744
+ let accepted = false;
745
+ let endHere = atEnd;
746
+ let newlineHere = atNewline;
747
+ const live = [];
748
+ for (const config of configs) {
749
+ const state = Math.floor(config / 3);
750
+ const constraint = config % 3;
751
+ if (state === this.compiled.accept) {
752
+ if (this.compiled.mode === "full" || constraint === END)
753
+ endHere = true;
754
+ else if (constraint === NEWLINE_OR_END)
755
+ newlineHere = true;
756
+ else
757
+ accepted = true;
758
+ }
759
+ if (constraint === END)
760
+ continue;
761
+ live.push(config);
762
+ }
763
+ const key = accepted
764
+ ? ACCEPTED
765
+ : `${live.join(",")}|${endHere ? 1 : 0}${newlineHere ? 1 : 0}`;
766
+ const existing = this.ids.get(key);
767
+ if (existing !== undefined)
768
+ return existing;
769
+ const id = this.keys.length;
770
+ this.ids.set(key, id);
771
+ this.keys.push(key);
772
+ this.sets.push(accepted ? [] : live);
773
+ this.flags.push({ accepted, atEnd: endHere, atNewline: newlineHere });
774
+ if (id >= MAX_PRODUCT_STATES)
775
+ throw new UnsupportedPattern("the pattern's automaton grew too large to decide");
776
+ return id;
777
+ }
778
+ accepts(id) {
779
+ const flag = this.flags[id];
780
+ return flag !== undefined && (flag.accepted || flag.atEnd || flag.atNewline);
781
+ }
782
+ step(id, unit) {
783
+ const memo = `${id}:${unit}`;
784
+ const known = this.moves.get(memo);
785
+ if (known !== undefined)
786
+ return known;
787
+ const flag = this.flags[id];
788
+ let result;
789
+ if (flag.accepted)
790
+ result = id;
791
+ else {
792
+ const next = [];
793
+ for (const config of this.sets[id] ?? []) {
794
+ const state = Math.floor(config / 3);
795
+ const constraint = config % 3;
796
+ if (constraint === NEWLINE_OR_END && unit !== 0x0a)
797
+ continue;
798
+ const after = constraint === NEWLINE_OR_END ? END : constraint;
799
+ for (const edge of this.compiled.nfa.edges[state] ?? [])
800
+ if (edge.type === "units" && contains(edge.ranges, unit))
801
+ next.push(this.config(edge.to, after));
802
+ }
803
+ // Search mode starts a new attempt at every position.
804
+ if (this.compiled.mode === "search")
805
+ next.push(this.config(this.compiled.start, FREE));
806
+ const atEnd = flag.atNewline && unit === 0x0a;
807
+ result = this.intern(this.closure(next, false), atEnd, false);
808
+ }
809
+ this.moves.set(memo, result);
810
+ return result;
811
+ }
812
+ /** The unit boundaries the automaton distinguishes. */
813
+ boundaries(into) {
814
+ for (const edges of this.compiled.nfa.edges)
815
+ for (const edge of edges)
816
+ if (edge.type === "units")
817
+ for (const [lo, hi] of edge.ranges) {
818
+ into.add(lo);
819
+ into.add(hi + 1);
820
+ }
821
+ into.add(0x0a);
822
+ into.add(0x0b);
823
+ }
824
+ }
825
+ function contains(ranges, unit) {
826
+ let lo = 0;
827
+ let hi = ranges.length - 1;
828
+ while (lo <= hi) {
829
+ const mid = (lo + hi) >> 1;
830
+ const [start, end] = ranges[mid];
831
+ if (unit < start)
832
+ hi = mid - 1;
833
+ else if (unit > end)
834
+ lo = mid + 1;
835
+ else
836
+ return true;
837
+ }
838
+ return false;
839
+ }
840
+ /** One representative unit per class of units no automaton tells apart. */
841
+ function minterms(automata) {
842
+ const cuts = new Set([0, 0xd800, 0xdc00, 0xe000, MAX_UNIT + 1]);
843
+ for (const automaton of automata)
844
+ automaton.boundaries(cuts);
845
+ const sorted = [...cuts].filter((cut) => cut <= MAX_UNIT + 1).sort((a, b) => a - b);
846
+ const representatives = [];
847
+ for (let index = 0; index < sorted.length - 1; index++) {
848
+ const lo = sorted[index];
849
+ const hi = sorted[index + 1] - 1;
850
+ // Prefer a printable character so a counterexample reads well.
851
+ const printable = [0x61, 0x41, 0x30, 0x20].find((code) => code >= lo && code <= hi);
852
+ const visible = lo <= 0x7e && hi >= 0x21 ? Math.max(lo, 0x21) : lo;
853
+ representatives.push(printable ?? visible);
854
+ }
855
+ // The search tries readable characters first, so among the shortest
856
+ // strings that tell two languages apart it reports a readable one.
857
+ const rank = (unit) => /[A-Za-z0-9]/.test(String.fromCharCode(unit))
858
+ ? 0
859
+ : unit >= 0x20 && unit <= 0x7e
860
+ ? 1
861
+ : unit >= 0xa0
862
+ ? 2
863
+ : 3;
864
+ return representatives.sort((x, y) => rank(x) - rank(y) || x - y);
865
+ }
866
+ // Well-formed UTF-16: 0 between scalar values, 1 after a high surrogate.
867
+ function wellFormedStep(state, unit) {
868
+ const high = unit >= 0xd800 && unit <= 0xdbff;
869
+ const low = unit >= 0xdc00 && unit <= 0xdfff;
870
+ if (state === 1)
871
+ return low ? 0 : null;
872
+ if (low)
873
+ return null;
874
+ return high ? 1 : 0;
875
+ }
876
+ function decide(left, right, relation) {
877
+ const a = new Determinizer(compileLanguage(left));
878
+ const b = new Determinizer(compileLanguage(right));
879
+ const alphabet = minterms([a, b]);
880
+ const start = { a: a.initial(), b: b.initial(), wf: 0, text: [] };
881
+ const seen = new Set([`${start.a},${start.b},0`]);
882
+ const queue = [start];
883
+ for (let head = 0; head < queue.length; head++) {
884
+ const node = queue[head];
885
+ if (node.wf === 0) {
886
+ const inA = a.accepts(node.a);
887
+ const inB = b.accepts(node.b);
888
+ const witness = String.fromCharCode(...node.text);
889
+ if (relation === "equal" && inA !== inB)
890
+ return {
891
+ holds: false,
892
+ witness,
893
+ reason: `${JSON.stringify(witness)} is in ${inA ? "the first" : "the second"} language only`,
894
+ };
895
+ if (relation === "disjoint" && inA && inB)
896
+ return { holds: false, witness, reason: `${JSON.stringify(witness)} is in both languages` };
897
+ if (relation === "subset" && inA && !inB)
898
+ return {
899
+ holds: false,
900
+ witness,
901
+ reason: `${JSON.stringify(witness)} is in the first language but not the second`,
902
+ };
903
+ }
904
+ for (const unit of alphabet) {
905
+ const wf = wellFormedStep(node.wf, unit);
906
+ if (wf === null)
907
+ continue;
908
+ const next = { a: a.step(node.a, unit), b: b.step(node.b, unit), wf };
909
+ const key = `${next.a},${next.b},${wf}`;
910
+ if (seen.has(key))
911
+ continue;
912
+ if (seen.size >= MAX_PRODUCT_STATES)
913
+ throw new UnsupportedPattern("the languages' product automaton grew too large to decide");
914
+ seen.add(key);
915
+ queue.push({ ...next, text: [...node.text, unit] });
916
+ }
917
+ }
918
+ return { holds: true };
919
+ }
920
+ /** Whether two languages hold exactly the same strings. */
921
+ // implements REQ-kibi-non-numeric-proof-kinds
922
+ export function languagesEqual(left, right) {
923
+ return decide(left, right, "equal");
924
+ }
925
+ /** Whether no string is in both languages. */
926
+ // implements REQ-kibi-non-numeric-proof-kinds
927
+ export function languagesDisjoint(left, right) {
928
+ return decide(left, right, "disjoint");
929
+ }
930
+ /** Whether every string of `left` is in `right`. */
931
+ // implements REQ-kibi-non-numeric-proof-kinds
932
+ export function languageIncluded(left, right) {
933
+ return decide(left, right, "subset");
934
+ }
935
+ /** Whether a language holds `text` (a well-formed string). */
936
+ // implements REQ-kibi-non-numeric-proof-kinds
937
+ export function languageAccepts(spec, text) {
938
+ const automaton = new Determinizer(compileLanguage(spec));
939
+ let state = automaton.initial();
940
+ for (let index = 0; index < text.length; index++)
941
+ state = automaton.step(state, text.charCodeAt(index));
942
+ return automaton.accepts(state);
943
+ }