ibex 0.2.0 → 0.4.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 (461) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +100 -272
  3. data/docs/architecture.md +187 -35
  4. data/docs/bison-import.md +103 -0
  5. data/docs/comparison-policy.md +127 -0
  6. data/docs/configuration-model.md +59 -0
  7. data/docs/configuration-report.md +28 -0
  8. data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
  9. data/docs/conflict-explanation-study.md +62 -0
  10. data/docs/construction-profiling.md +157 -0
  11. data/docs/cst-migration.md +3 -4
  12. data/docs/cst.md +24 -0
  13. data/docs/decisions/0000-template.md +24 -0
  14. data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
  15. data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
  16. data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
  17. data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
  18. data/docs/decisions/0005-contained-grammar-composition.md +34 -0
  19. data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
  20. data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
  21. data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
  22. data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
  23. data/docs/decisions/0010-committed-runtime-observation.md +34 -0
  24. data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
  25. data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
  26. data/docs/decisions/0013-transactional-generation-publication.md +37 -0
  27. data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
  28. data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
  29. data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
  30. data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
  31. data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
  32. data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
  33. data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
  34. data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
  35. data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
  36. data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
  37. data/docs/decisions/0024-direct-ielr-construction.md +42 -0
  38. data/docs/decisions/README.md +74 -0
  39. data/docs/declarative-configuration.md +274 -0
  40. data/docs/development.md +196 -2
  41. data/docs/direct-ielr-decision.md +120 -0
  42. data/docs/direct-multi-entry-decision.md +82 -0
  43. data/docs/editor-setup.md +2 -1
  44. data/docs/error-ux-review-rubric-v1.md +117 -0
  45. data/docs/error-ux-reviews/v1/records/README.md +22 -0
  46. data/docs/error-ux-round2-review-status-v1.json +27 -0
  47. data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
  48. data/docs/error-ux-round2-v1.json +1617 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +54 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +184 -13
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/impact.md +101 -0
  56. data/docs/ja/bison-import.md +51 -0
  57. data/docs/ja/stable-api.md +53 -0
  58. data/docs/lexer-construction-profile.md +109 -0
  59. data/docs/lexer-migration.md +5 -0
  60. data/docs/maturity.md +129 -0
  61. data/docs/project-site-strategy.md +54 -0
  62. data/docs/racc-migration-evidence.md +45 -0
  63. data/docs/racc-migration.md +11 -0
  64. data/docs/release-readiness.md +126 -68
  65. data/docs/repair-semantics.md +123 -0
  66. data/docs/runtime-abi-evolution.md +310 -0
  67. data/docs/stability.md +145 -34
  68. data/docs/status.md +43 -0
  69. data/docs/syntax-sessions.md +183 -0
  70. data/docs/table-artifact.md +125 -0
  71. data/docs/test-interactions.md +219 -0
  72. data/docs/verification-report.md +137 -0
  73. data/docs/verifier-trust-boundary.md +199 -0
  74. data/docs/workloads.md +132 -0
  75. data/lib/ibex/analysis/digraph.rb +128 -0
  76. data/lib/ibex/analysis/sets.rb +14 -0
  77. data/lib/ibex/analysis.rb +1 -0
  78. data/lib/ibex/bison_import/importer.rb +516 -0
  79. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  80. data/lib/ibex/bison_import.rb +200 -0
  81. data/lib/ibex/bounded_subprocess.rb +171 -0
  82. data/lib/ibex/cli/ambiguity.rb +14 -5
  83. data/lib/ibex/cli/analysis.rb +134 -0
  84. data/lib/ibex/cli/bison_import.rb +121 -0
  85. data/lib/ibex/cli/config.rb +128 -0
  86. data/lib/ibex/cli/diagnostics.rb +31 -11
  87. data/lib/ibex/cli/documentation.rb +11 -8
  88. data/lib/ibex/cli/equiv.rb +169 -0
  89. data/lib/ibex/cli/error_messages.rb +11 -4
  90. data/lib/ibex/cli/explain.rb +14 -5
  91. data/lib/ibex/cli/fix.rb +203 -0
  92. data/lib/ibex/cli/formatting.rb +11 -10
  93. data/lib/ibex/cli/fuzz.rb +200 -0
  94. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  95. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  96. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  97. data/lib/ibex/cli/grammar_tests.rb +32 -14
  98. data/lib/ibex/cli/impact.rb +393 -0
  99. data/lib/ibex/cli/ir_tools.rb +5 -61
  100. data/lib/ibex/cli/outputs.rb +60 -33
  101. data/lib/ibex/cli/reduce.rb +202 -0
  102. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  103. data/lib/ibex/cli/samples.rb +49 -23
  104. data/lib/ibex/cli/verify.rb +116 -0
  105. data/lib/ibex/cli/watch.rb +6 -5
  106. data/lib/ibex/cli.rb +362 -56
  107. data/lib/ibex/codegen/action_locations.rb +1 -1
  108. data/lib/ibex/codegen/action_method_source.rb +5 -6
  109. data/lib/ibex/codegen/action_source.rb +3 -2
  110. data/lib/ibex/codegen/ambiguity.rb +5 -4
  111. data/lib/ibex/codegen/explain.rb +123 -55
  112. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  113. data/lib/ibex/codegen/rbs.rb +15 -7
  114. data/lib/ibex/codegen/report.rb +25 -13
  115. data/lib/ibex/codegen/ruby.rb +28 -64
  116. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  117. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  118. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  119. data/lib/ibex/configuration/explanation.rb +421 -0
  120. data/lib/ibex/configuration/inspector.rb +232 -0
  121. data/lib/ibex/configuration.rb +580 -0
  122. data/lib/ibex/coverage/collector.rb +21 -11
  123. data/lib/ibex/coverage/event_stream.rb +13 -7
  124. data/lib/ibex/coverage/report.rb +26 -18
  125. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  126. data/lib/ibex/delta_reducer.rb +99 -0
  127. data/lib/ibex/diff.rb +141 -0
  128. data/lib/ibex/equiv/machine.rb +135 -0
  129. data/lib/ibex/equiv.rb +373 -0
  130. data/lib/ibex/fix.rb +557 -0
  131. data/lib/ibex/frontend/ast.rb +22 -2
  132. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  133. data/lib/ibex/frontend/diagnostic.rb +1 -1
  134. data/lib/ibex/frontend/formatter.rb +72 -26
  135. data/lib/ibex/frontend/generated_parser.rb +147 -117
  136. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  137. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  138. data/lib/ibex/frontend/generation.rb +1 -1
  139. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  140. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  141. data/lib/ibex/frontend/regenerator.rb +1 -0
  142. data/lib/ibex/frontend/resolution.rb +2 -1
  143. data/lib/ibex/frontend/resolver.rb +1 -1
  144. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  145. data/lib/ibex/frontend/source_cursor.rb +2 -2
  146. data/lib/ibex/frontend/source_span.rb +9 -1
  147. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  148. data/lib/ibex/frontend/token_adapter.rb +10 -0
  149. data/lib/ibex/frontend.rb +1 -0
  150. data/lib/ibex/fuzz.rb +219 -0
  151. data/lib/ibex/generation_input.rb +1 -1
  152. data/lib/ibex/generation_manifest.rb +36 -25
  153. data/lib/ibex/generation_transaction.rb +1 -1
  154. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  155. data/lib/ibex/generation_transaction_validation.rb +2 -2
  156. data/lib/ibex/grammar_tests.rb +1 -1
  157. data/lib/ibex/impact/action_impact.rb +148 -0
  158. data/lib/ibex/impact/automaton_impact.rb +57 -0
  159. data/lib/ibex/impact/coverage_impact.rb +53 -0
  160. data/lib/ibex/impact/graph.rb +132 -0
  161. data/lib/ibex/impact/propagation.rb +147 -0
  162. data/lib/ibex/impact/report.rb +273 -0
  163. data/lib/ibex/impact/seeds.rb +69 -0
  164. data/lib/ibex/impact/severity.rb +139 -0
  165. data/lib/ibex/impact.rb +16 -0
  166. data/lib/ibex/ir/automaton_ir.rb +61 -19
  167. data/lib/ibex/ir/grammar_ir.rb +76 -45
  168. data/lib/ibex/ir/lexer_ir.rb +5 -5
  169. data/lib/ibex/ir/parser_contract.rb +135 -0
  170. data/lib/ibex/ir/serialize.rb +203 -76
  171. data/lib/ibex/ir/validator/automaton.rb +64 -34
  172. data/lib/ibex/ir/validator/base.rb +51 -33
  173. data/lib/ibex/ir/validator/grammar.rb +113 -83
  174. data/lib/ibex/ir/validator/lexer.rb +2 -2
  175. data/lib/ibex/ir/validator.rb +2 -1
  176. data/lib/ibex/ir.rb +16 -3
  177. data/lib/ibex/lalr/build_metrics.rb +50 -2
  178. data/lib/ibex/lalr/builder.rb +191 -56
  179. data/lib/ibex/lalr/conflict_search.rb +15 -4
  180. data/lib/ibex/lalr/counterexample.rb +19 -14
  181. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  182. data/lib/ibex/lalr/goto_follows.rb +229 -0
  183. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  184. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  185. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  186. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  187. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  188. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  189. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  190. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  191. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  192. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  193. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  194. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  195. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  196. data/lib/ibex/lalr.rb +52 -1
  197. data/lib/ibex/location.rb +3 -3
  198. data/lib/ibex/lsp/document_handlers.rb +9 -7
  199. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  200. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  201. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  202. data/lib/ibex/lsp/position_codec.rb +6 -2
  203. data/lib/ibex/lsp/request_handlers.rb +3 -2
  204. data/lib/ibex/lsp/request_support.rb +14 -7
  205. data/lib/ibex/lsp/server.rb +13 -11
  206. data/lib/ibex/lsp/symbol_index.rb +13 -13
  207. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  208. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  209. data/lib/ibex/lsp/transport.rb +13 -3
  210. data/lib/ibex/lsp/workspace.rb +5 -4
  211. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  212. data/lib/ibex/lsp.rb +1 -0
  213. data/lib/ibex/messages/en.yml +28 -0
  214. data/lib/ibex/messages/ja.yml +28 -0
  215. data/lib/ibex/messages.rb +68 -0
  216. data/lib/ibex/metrics.rb +175 -0
  217. data/lib/ibex/normalize/declarations.rb +2 -0
  218. data/lib/ibex/normalize/diagnostics.rb +63 -38
  219. data/lib/ibex/normalize/expander.rb +5 -4
  220. data/lib/ibex/normalize/expression.rb +8 -6
  221. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  222. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  223. data/lib/ibex/normalize/lexer.rb +3 -3
  224. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  225. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  226. data/lib/ibex/normalize/parameters.rb +31 -4
  227. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  228. data/lib/ibex/normalize.rb +10 -20
  229. data/lib/ibex/racc_migration/report.rb +6 -3
  230. data/lib/ibex/rake_task.rb +1 -1
  231. data/lib/ibex/samples.rb +43 -13
  232. data/lib/ibex/table_artifact/builder.rb +266 -0
  233. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  234. data/lib/ibex/table_artifact/document.rb +39 -0
  235. data/lib/ibex/table_artifact/executor.rb +187 -0
  236. data/lib/ibex/table_artifact/serializer.rb +57 -0
  237. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  238. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  239. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  240. data/lib/ibex/table_artifact/validator.rb +253 -0
  241. data/lib/ibex/table_artifact.rb +88 -0
  242. data/lib/ibex/table_simulation/result.rb +6 -2
  243. data/lib/ibex/table_simulation/step.rb +3 -1
  244. data/lib/ibex/tables.rb +14 -0
  245. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  246. data/lib/ibex/verification_report/builder.rb +159 -0
  247. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  248. data/lib/ibex/verification_report/logical_path.rb +68 -0
  249. data/lib/ibex/verification_report/validator.rb +329 -0
  250. data/lib/ibex/verification_report.rb +69 -0
  251. data/lib/ibex/verify/action_correspondence.rb +138 -0
  252. data/lib/ibex/verify/language_witness.rb +295 -0
  253. data/lib/ibex/verify/reference_collection.rb +190 -0
  254. data/lib/ibex/verify/result.rb +73 -0
  255. data/lib/ibex/verify/verifier.rb +598 -0
  256. data/lib/ibex/verify.rb +15 -0
  257. data/lib/ibex/version.rb +1 -1
  258. data/lib/ibex/watch/runner.rb +10 -1
  259. data/lib/ibex/watch/source_snapshot.rb +13 -8
  260. data/lib/ibex.rb +11 -0
  261. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  262. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  263. data/schema/bison-import-v1.schema.json +106 -0
  264. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  265. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  266. data/schema/construction-profile-v1.schema.json +779 -0
  267. data/schema/diff-v1.schema.json +41 -0
  268. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  269. data/schema/equiv-v1.schema.json +64 -0
  270. data/schema/error-ux-review-v1.schema.json +609 -0
  271. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  272. data/schema/error-ux-round2-v1.schema.json +519 -0
  273. data/schema/explain-v1.schema.json +63 -1
  274. data/schema/fix-v1.schema.json +64 -0
  275. data/schema/fix-v2.schema.json +66 -0
  276. data/schema/fix-v3.schema.json +101 -0
  277. data/schema/fuzz-regression-v1.schema.json +79 -0
  278. data/schema/fuzz-v1.schema.json +114 -0
  279. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  280. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  281. data/schema/grammar-ir.schema.json +271 -0
  282. data/schema/ielr-benchmark-v1.schema.json +85 -0
  283. data/schema/impact-v1.schema.json +208 -0
  284. data/schema/lexer-profile-v1.schema.json +491 -0
  285. data/schema/metrics-v1.schema.json +60 -0
  286. data/schema/reduce-v1.schema.json +22 -0
  287. data/schema/reduce-v2.schema.json +63 -0
  288. data/schema/table-artifact-v1.schema.json +377 -0
  289. data/schema/verification-report-v1.schema.json +222 -0
  290. data/schema/verify-v1.schema.json +42 -0
  291. data/sig/ibex/analysis/digraph.rbs +19 -0
  292. data/sig/ibex/analysis/sets.rbs +10 -0
  293. data/sig/ibex/bison_import/importer.rbs +140 -0
  294. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  295. data/sig/ibex/bison_import.rbs +95 -0
  296. data/sig/ibex/bounded_subprocess.rbs +76 -0
  297. data/sig/ibex/cli/ambiguity.rbs +6 -0
  298. data/sig/ibex/cli/analysis.rbs +43 -0
  299. data/sig/ibex/cli/bison_import.rbs +32 -0
  300. data/sig/ibex/cli/config.rbs +35 -0
  301. data/sig/ibex/cli/diagnostics.rbs +16 -3
  302. data/sig/ibex/cli/documentation.rbs +5 -1
  303. data/sig/ibex/cli/equiv.rbs +49 -0
  304. data/sig/ibex/cli/error_messages.rbs +6 -0
  305. data/sig/ibex/cli/explain.rbs +6 -0
  306. data/sig/ibex/cli/fix.rbs +53 -0
  307. data/sig/ibex/cli/formatting.rbs +5 -1
  308. data/sig/ibex/cli/fuzz.rbs +47 -0
  309. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  310. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  311. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  312. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  313. data/sig/ibex/cli/impact.rbs +95 -0
  314. data/sig/ibex/cli/ir_tools.rbs +7 -19
  315. data/sig/ibex/cli/outputs.rbs +9 -3
  316. data/sig/ibex/cli/reduce.rbs +60 -0
  317. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  318. data/sig/ibex/cli/samples.rbs +8 -0
  319. data/sig/ibex/cli/verify.rbs +28 -0
  320. data/sig/ibex/cli/watch.rbs +2 -2
  321. data/sig/ibex/cli.rbs +79 -5
  322. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  323. data/sig/ibex/codegen/explain.rbs +45 -25
  324. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  325. data/sig/ibex/codegen/rbs.rbs +12 -10
  326. data/sig/ibex/codegen/report.rbs +12 -12
  327. data/sig/ibex/codegen/ruby.rbs +18 -15
  328. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  329. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  330. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  331. data/sig/ibex/configuration/explanation.rbs +195 -0
  332. data/sig/ibex/configuration/inspector.rbs +53 -0
  333. data/sig/ibex/configuration.rbs +242 -0
  334. data/sig/ibex/coverage/collector.rbs +16 -14
  335. data/sig/ibex/coverage/event_stream.rbs +12 -10
  336. data/sig/ibex/coverage/report.rbs +20 -18
  337. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  338. data/sig/ibex/delta_reducer.rbs +38 -0
  339. data/sig/ibex/diff.rbs +47 -0
  340. data/sig/ibex/equiv/machine.rbs +54 -0
  341. data/sig/ibex/equiv.rbs +129 -0
  342. data/sig/ibex/fix.rbs +138 -0
  343. data/sig/ibex/frontend/ast.rbs +33 -7
  344. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  345. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  346. data/sig/ibex/frontend/formatter.rbs +38 -30
  347. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  348. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  349. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  350. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  351. data/sig/ibex/frontend/resolution.rbs +3 -2
  352. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  353. data/sig/ibex/frontend/source_span.rbs +5 -2
  354. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  355. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  356. data/sig/ibex/fuzz.rbs +71 -0
  357. data/sig/ibex/generation_input.rbs +2 -2
  358. data/sig/ibex/generation_manifest.rbs +35 -32
  359. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  360. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  361. data/sig/ibex/grammar_tests.rbs +2 -2
  362. data/sig/ibex/impact/action_impact.rbs +58 -0
  363. data/sig/ibex/impact/automaton_impact.rbs +37 -0
  364. data/sig/ibex/impact/coverage_impact.rbs +28 -0
  365. data/sig/ibex/impact/graph.rbs +67 -0
  366. data/sig/ibex/impact/propagation.rbs +56 -0
  367. data/sig/ibex/impact/report.rbs +107 -0
  368. data/sig/ibex/impact/seeds.rbs +36 -0
  369. data/sig/ibex/impact/severity.rbs +55 -0
  370. data/sig/ibex/impact.rbs +6 -0
  371. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  372. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  373. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  374. data/sig/ibex/ir/parser_contract.rbs +69 -0
  375. data/sig/ibex/ir/serialize.rbs +63 -28
  376. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  377. data/sig/ibex/ir/validator/base.rbs +35 -31
  378. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  379. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  380. data/sig/ibex/ir.rbs +9 -3
  381. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  382. data/sig/ibex/lalr/builder.rbs +75 -24
  383. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  384. data/sig/ibex/lalr/counterexample.rbs +17 -14
  385. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  386. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  387. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  388. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  389. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  390. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  391. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  392. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  393. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  394. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  395. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  396. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  397. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  398. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  399. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  400. data/sig/ibex/lalr.rbs +11 -1
  401. data/sig/ibex/location.rbs +6 -6
  402. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  403. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  404. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  405. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  406. data/sig/ibex/lsp/position_codec.rbs +8 -4
  407. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  408. data/sig/ibex/lsp/request_support.rbs +8 -8
  409. data/sig/ibex/lsp/server.rbs +16 -16
  410. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  411. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  412. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  413. data/sig/ibex/lsp/transport.rbs +18 -6
  414. data/sig/ibex/lsp/workspace.rbs +3 -2
  415. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  416. data/sig/ibex/messages.rbs +26 -0
  417. data/sig/ibex/metrics.rbs +50 -0
  418. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  419. data/sig/ibex/normalize/expander.rbs +2 -2
  420. data/sig/ibex/normalize/expression.rbs +10 -8
  421. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  422. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  423. data/sig/ibex/normalize/lexer.rbs +4 -4
  424. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  425. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  426. data/sig/ibex/normalize/parameters.rbs +13 -8
  427. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  428. data/sig/ibex/normalize.rbs +11 -5
  429. data/sig/ibex/racc_migration/report.rbs +8 -6
  430. data/sig/ibex/rake_task.rbs +1 -1
  431. data/sig/ibex/samples.rbs +13 -4
  432. data/sig/ibex/table_artifact/builder.rbs +93 -0
  433. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  434. data/sig/ibex/table_artifact/document.rbs +27 -0
  435. data/sig/ibex/table_artifact/executor.rbs +66 -0
  436. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  437. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  438. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  439. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  440. data/sig/ibex/table_artifact/validator.rbs +66 -0
  441. data/sig/ibex/table_artifact.rbs +32 -0
  442. data/sig/ibex/table_simulation/result.rbs +8 -4
  443. data/sig/ibex/table_simulation/step.rbs +4 -2
  444. data/sig/ibex/tables.rbs +10 -9
  445. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  446. data/sig/ibex/verification_report/builder.rbs +53 -0
  447. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  448. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  449. data/sig/ibex/verification_report/validator.rbs +114 -0
  450. data/sig/ibex/verification_report.rbs +44 -0
  451. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  452. data/sig/ibex/verify/language_witness.rbs +112 -0
  453. data/sig/ibex/verify/reference_collection.rbs +63 -0
  454. data/sig/ibex/verify/result.rbs +52 -0
  455. data/sig/ibex/verify/verifier.rbs +170 -0
  456. data/sig/ibex/verify.rbs +6 -0
  457. data/sig/ibex/watch/runner.rbs +12 -2
  458. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  459. metadata +254 -12
  460. data/lib/ibex/ir/migration.rb +0 -120
  461. data/sig/ibex/ir/migration.rbs +0 -34
@@ -6,7 +6,11 @@ require "json"
6
6
 
7
7
  module Ibex
8
8
  # Deterministic description of one published parser generation.
9
+ # rubocop:disable Metrics/ModuleLength -- manifest schema validation is kept in one auditable unit.
9
10
  module GenerationManifest
11
+ # @rbs!
12
+ # type json_value = String | Integer | Float | bool | nil | Array[json_value] | Hash[String, json_value]
13
+
10
14
  SCHEMA_VERSION = 1 #: Integer
11
15
  IDENTIFIER = "generation" #: String
12
16
  ROOT_KEYS = %w[ibex_manifest schema_version input options artifacts].freeze #: Array[String]
@@ -16,7 +20,7 @@ module Ibex
16
20
 
17
21
  module_function
18
22
 
19
- # @rbs (ArtifactSet artifacts, source_records: Array[GenerationInput], options: Hash[String, untyped]) -> String
23
+ # @rbs (ArtifactSet artifacts, source_records: Array[GenerationInput], options: Hash[String, Object?]) -> String
20
24
  def render(artifacts, source_records:, options:)
21
25
  files = source_records.map(&:to_h)
22
26
  document = {
@@ -34,24 +38,26 @@ module Ibex
34
38
  end
35
39
 
36
40
  # Validate the manifest shape and, by default, every published artifact digest.
37
- # @rbs (String source, ?verify_artifacts: bool) -> Hash[String, untyped]
41
+ # @rbs (String source, ?verify_artifacts: bool) -> Hash[String, json_value]
38
42
  def validate(source, verify_artifacts: true)
39
- document = JSON.parse(source)
43
+ document = JSON.parse(source) #: Object?
40
44
  validate_document(document)
41
- verify_entries(document.fetch("artifacts")) if verify_artifacts
42
- document
45
+ document_hash = document #: Hash[String, json_value]
46
+ artifacts = document_hash.fetch("artifacts") #: Array[Object?]
47
+ verify_entries(artifacts) if verify_artifacts
48
+ document_hash
43
49
  rescue JSON::ParserError, KeyError, TypeError, ArgumentError => e
44
50
  raise Ibex::Error, "(manifest):1:1: invalid generation manifest: #{e.message}"
45
51
  end
46
52
 
47
- # @rbs (String path, ?verify_artifacts: bool) -> Hash[String, untyped]
53
+ # @rbs (String path, ?verify_artifacts: bool) -> Hash[String, json_value]
48
54
  def validate_file(path, verify_artifacts: true)
49
55
  validate(File.binread(path), verify_artifacts: verify_artifacts)
50
56
  rescue SystemCallError, ArgumentError => e
51
57
  raise Ibex::Error, "#{path}:1:1: cannot read generation manifest: #{e.message}"
52
58
  end
53
59
 
54
- # @rbs (Artifact artifact) -> Hash[String, untyped]
60
+ # @rbs (Artifact artifact) -> Hash[String, json_value]
55
61
  def artifact_entry(artifact)
56
62
  {
57
63
  "kind" => artifact.kind.to_s,
@@ -62,19 +68,20 @@ module Ibex
62
68
  end
63
69
  private_class_method :artifact_entry
64
70
 
65
- # @rbs (Array[Hash[String, untyped]] files) -> String
71
+ # @rbs (Array[Hash[String, json_value]] files) -> String
66
72
  def input_digest(files)
67
73
  digest = Digest::SHA256.new
68
74
  files.each do |entry|
69
- digest << entry.fetch("path") << "\0"
70
- digest << entry.fetch("sha256") << "\0"
71
- digest << entry.fetch("bytesize").to_s << "\0"
75
+ path = entry.fetch("path") # @type var path: String
76
+ sha256 = entry.fetch("sha256") # @type var sha256: String
77
+ bytesize = entry.fetch("bytesize") # @type var bytesize: Integer
78
+ digest << path << "\0" << sha256 << "\0" << bytesize.to_s << "\0"
72
79
  end
73
80
  digest.hexdigest
74
81
  end
75
82
  private_class_method :input_digest
76
83
 
77
- # @rbs (Hash[String, untyped] value) -> Hash[String, untyped]
84
+ # @rbs (Hash[String, Object?] value) -> Hash[String, Object?]
78
85
  def sorted_hash(value)
79
86
  value.keys.sort.to_h do |key|
80
87
  child = value.fetch(key)
@@ -84,7 +91,7 @@ module Ibex
84
91
  end
85
92
  private_class_method :sorted_hash
86
93
 
87
- # @rbs (untyped document) -> void
94
+ # @rbs (Object? document) -> void
88
95
  def validate_document(document)
89
96
  raise TypeError, "document must be an object" unless document.is_a?(Hash)
90
97
 
@@ -100,14 +107,14 @@ module Ibex
100
107
  end
101
108
  private_class_method :validate_document
102
109
 
103
- # @rbs (Hash[untyped, untyped] document) -> void
110
+ # @rbs (Hash[Object?, Object?] document) -> void
104
111
  def validate_identity(document)
105
112
  raise TypeError, "unsupported manifest identifier" unless document["ibex_manifest"] == IDENTIFIER
106
113
  raise TypeError, "unsupported schema_version" unless document["schema_version"] == SCHEMA_VERSION
107
114
  end
108
115
  private_class_method :validate_identity
109
116
 
110
- # @rbs (untyped input) -> void
117
+ # @rbs (Object? input) -> void
111
118
  def validate_input(input)
112
119
  raise TypeError, "input must be an object" unless input.is_a?(Hash)
113
120
 
@@ -125,13 +132,13 @@ module Ibex
125
132
  end
126
133
  private_class_method :validate_input
127
134
 
128
- # @rbs (Array[untyped] entries, String label, ?require_kind: bool) -> void
135
+ # @rbs (Array[Object?] entries, String label, ?require_kind: bool) -> void
129
136
  def validate_entries(entries, label, require_kind: false)
130
137
  entries.each { |entry| validate_entry(entry, label, require_kind) }
131
138
  end
132
139
  private_class_method :validate_entries
133
140
 
134
- # @rbs (untyped entry, String label, bool require_kind) -> void
141
+ # @rbs (Object? entry, String label, bool require_kind) -> void
135
142
  def validate_entry(entry, label, require_kind)
136
143
  raise TypeError, "#{label} must be an object" unless entry.is_a?(Hash)
137
144
 
@@ -146,7 +153,7 @@ module Ibex
146
153
  end
147
154
  private_class_method :validate_entry
148
155
 
149
- # @rbs (untyped path, String label) -> void
156
+ # @rbs (Object? path, String label) -> void
150
157
  def validate_path(path, label)
151
158
  raise TypeError, "#{label} path must be a non-empty string" unless path.is_a?(String) && !path.empty?
152
159
  return unless path.include?("\0") || path.match?(/[[:cntrl:]]/)
@@ -155,7 +162,7 @@ module Ibex
155
162
  end
156
163
  private_class_method :validate_path
157
164
 
158
- # @rbs (untyped kind, String label) -> void
165
+ # @rbs (Object? kind, String label) -> void
159
166
  def validate_kind(kind, label)
160
167
  return if kind.is_a?(String) && !kind.empty?
161
168
 
@@ -163,7 +170,7 @@ module Ibex
163
170
  end
164
171
  private_class_method :validate_kind
165
172
 
166
- # @rbs (untyped digest, String label) -> void
173
+ # @rbs (Object? digest, String label) -> void
167
174
  def validate_digest(digest, label)
168
175
  return if digest.is_a?(String) && digest.match?(/\A[0-9a-f]{64}\z/)
169
176
 
@@ -171,7 +178,7 @@ module Ibex
171
178
  end
172
179
  private_class_method :validate_digest
173
180
 
174
- # @rbs (Hash[untyped, untyped] value, Array[String] expected, String label) -> void
181
+ # @rbs (Hash[Object?, Object?] value, Array[String] expected, String label) -> void
175
182
  def exact_keys!(value, expected, label)
176
183
  keys = value.keys
177
184
  raise TypeError, "#{label} keys must be strings" unless keys.all?(String)
@@ -183,18 +190,22 @@ module Ibex
183
190
  end
184
191
  private_class_method :exact_keys!
185
192
 
186
- # @rbs (Array[untyped] entries) -> void
193
+ # @rbs (Array[Object?] entries) -> void
187
194
  def verify_entries(entries)
188
195
  entries.each do |entry|
189
- path = entry.fetch("path")
196
+ entry_hash = entry #: Hash[String, json_value]
197
+ path = entry_hash.fetch("path") #: String
190
198
  source = File.binread(path)
191
- raise TypeError, "artifact bytesize mismatch for #{path}" unless source.bytesize == entry.fetch("bytesize")
199
+ bytesize = entry_hash.fetch("bytesize") #: Integer
200
+ sha256 = entry_hash.fetch("sha256") #: String
201
+ raise TypeError, "artifact bytesize mismatch for #{path}" unless source.bytesize == bytesize
192
202
  raise TypeError, "artifact digest mismatch for #{path}" unless
193
- Digest::SHA256.hexdigest(source) == entry.fetch("sha256")
203
+ Digest::SHA256.hexdigest(source) == sha256
194
204
  end
195
205
  rescue SystemCallError => e
196
206
  raise TypeError, "cannot read artifact: #{e.message}"
197
207
  end
198
208
  private_class_method :verify_entries
199
209
  end
210
+ # rubocop:enable Metrics/ModuleLength
200
211
  end
@@ -37,7 +37,7 @@ module Ibex
37
37
  @lock_sleeper = lock_sleeper
38
38
  @locks = [] #: Array[File]
39
39
  @locked_paths = [] #: Array[String]
40
- @records = [] #: Array[Hash[Symbol, untyped]]
40
+ @records = [] #: Array[Hash[Symbol, Object?]]
41
41
  @committed = false
42
42
  @temporary_sequence = 0
43
43
  end
@@ -57,14 +57,14 @@ module Ibex
57
57
  failures
58
58
  end
59
59
 
60
- # @rbs (Hash[Symbol, untyped] record) -> void
60
+ # @rbs (Hash[Symbol, Object?] record) -> void
61
61
  def rollback_record(record)
62
- target = record.fetch(:target)
62
+ target = record.fetch(:target) #: String
63
63
  if record.fetch(:backed_up)
64
64
  if record.fetch(:installed)
65
- File.rename(record.fetch(:backup), target)
65
+ File.rename(record.fetch(:backup).to_s, target)
66
66
  else
67
- File.unlink(record.fetch(:backup))
67
+ File.unlink(record.fetch(:backup).to_s)
68
68
  end
69
69
  record[:backup] = nil
70
70
  record[:backed_up] = false
@@ -170,9 +170,9 @@ module Ibex
170
170
  "(generation):1:1: cannot revalidate artifact target #{phase}: #{e.message}"
171
171
  end
172
172
 
173
- # @rbs (Hash[Symbol, untyped] record, Array[String] sources, String phase) -> void
173
+ # @rbs (Hash[Symbol, Object?] record, Array[String] sources, String phase) -> void
174
174
  def validate_current_target!(record, sources, phase)
175
- artifact = record.fetch(:artifact)
175
+ artifact = record.fetch(:artifact) #: Artifact
176
176
  current = resolved_target(artifact.path, validate_links: false)
177
177
  unless current == record.fetch(:target)
178
178
  raise GenerationTransaction::SourceChanged,
@@ -182,7 +182,7 @@ module Ibex
182
182
  raise Ibex::Error, "(test):1:1: invalid grammar test process result: #{e.message}"
183
183
  end
184
184
 
185
- # @rbs (IR::grammar_test test, untyped document) -> Result
185
+ # @rbs (IR::grammar_test test, Object? document) -> Result
186
186
  def build_result(test, document)
187
187
  unless document.is_a?(Hash) && %w[accept reject error].include?(document["actual"])
188
188
  raise Ibex::Error, "(test):1:1: grammar test process returned an invalid case result"
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Impact
6
+ # Checks only structured action metadata; action source remains opaque.
7
+ class ActionImpact
8
+ # @rbs @before: IR::Grammar
9
+ # @rbs @after: IR::Grammar
10
+ # @rbs @affected_names: Array[String]?
11
+ attr_reader :findings #: Array[Hash[Symbol, Object?]]
12
+
13
+ # @rbs (IR::Grammar before, IR::Grammar after, ?affected_names: Array[String]?) -> void
14
+ def initialize(before, after, affected_names: nil)
15
+ @before = before
16
+ @after = after
17
+ @affected_names = affected_names
18
+ @findings = compare
19
+ freeze
20
+ end
21
+
22
+ # @rbs () -> Array[Hash[Symbol, Object?]]
23
+ def to_a
24
+ @findings
25
+ end
26
+
27
+ private
28
+
29
+ # @rbs () -> Array[Hash[Symbol, Object?]]
30
+ def compare
31
+ names = @after.nonterminals.map(&:name)
32
+ names &= @affected_names if @affected_names
33
+ names.sort.flat_map { |name| compare_rule(name) }.sort_by { |finding| finding.fetch(:production) }
34
+ end
35
+
36
+ # @rbs (String name) -> Array[Hash[Symbol, Object?]]
37
+ def compare_rule(name)
38
+ before = productions_for(@before, name)
39
+ after = productions_for(@after, name)
40
+ pair_productions(before, after).filter_map do |previous, production|
41
+ next if previous.rhs.length == production.rhs.length
42
+
43
+ finding_for(previous, production)
44
+ end
45
+ end
46
+
47
+ # @rbs (Array[IR::Production] before, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]
48
+ def pair_productions(before, after)
49
+ unmatched = before.dup
50
+ pairs = pair_exact_productions(unmatched, after)
51
+ pair_by_location(unmatched, after, pairs)
52
+ end
53
+
54
+ # @rbs (Array[IR::Production] unmatched, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]
55
+ def pair_exact_productions(unmatched, after)
56
+ pairs = [] #: Array[[IR::Production, IR::Production]]
57
+
58
+ after.each do |production|
59
+ index = unmatched.index do |candidate|
60
+ production_signature(@before, candidate) == production_signature(@after, production)
61
+ end
62
+ next unless index
63
+
64
+ pairs << [unmatched.delete_at(index), production]
65
+ end
66
+
67
+ pairs
68
+ end
69
+
70
+ # @rbs (Array[IR::Production] unmatched, Array[IR::Production] after,
71
+ # Array[[IR::Production, IR::Production]]) -> Array[[IR::Production, IR::Production]]
72
+ def pair_by_location(unmatched, after, pairs)
73
+ after.each do |production|
74
+ next if pairs.any? { |_, candidate| candidate.equal?(production) }
75
+
76
+ index = unmatched.index do |candidate|
77
+ location = action_location_identity(production)
78
+ location && location == action_location_identity(candidate)
79
+ end
80
+ next unless index
81
+
82
+ pairs << [unmatched.delete_at(index), production]
83
+ end
84
+
85
+ pairs
86
+ end
87
+
88
+ # @rbs (IR::Grammar grammar, String name) -> Array[IR::Production]
89
+ def productions_for(grammar, name)
90
+ lhs = grammar.symbol(name)&.id
91
+ return [] unless lhs
92
+
93
+ grammar.productions.select { |production| production.lhs == lhs }
94
+ end
95
+
96
+ # @rbs (IR::Grammar grammar, IR::Production production) -> [Array[String], String?]
97
+ def production_signature(grammar, production)
98
+ rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
99
+ precedence = grammar.symbol_by_id(production.precedence_override)&.name
100
+ [rhs, precedence]
101
+ end
102
+
103
+ # @rbs (IR::Production production) -> [Integer, Integer]?
104
+ def action_location_identity(production)
105
+ location = production.origin[:loc] || production.action&.location #: IR::location?
106
+ return unless location
107
+
108
+ line = location[:line]
109
+ column = location[:column]
110
+ return unless line && column
111
+
112
+ [line, column]
113
+ end
114
+
115
+ # @rbs (IR::Production before, IR::Production after) -> Hash[Symbol, Object?]
116
+ def finding_for(before, after)
117
+ action = after.action
118
+ reason, severity = finding_reason(action, before.rhs.length, after.rhs.length)
119
+ {
120
+ production: production_name(@after, after), severity: severity,
121
+ reason: reason,
122
+ context_length: { before: before.rhs.length, after: after.rhs.length },
123
+ loc: after.action&.location || after.origin[:loc]
124
+ }
125
+ end
126
+
127
+ # @rbs (IR::Action?, Integer, Integer) -> [String, String]
128
+ def finding_reason(action, before_length, after_length)
129
+ return %w[named_ref_index_out_of_range high] if out_of_range?(action, after_length)
130
+ return %w[context_length_stale high] if action&.context_length == before_length
131
+
132
+ %w[rhs_length_changed medium]
133
+ end
134
+
135
+ # @rbs (IR::Action?, Integer) -> bool
136
+ def out_of_range?(action, length)
137
+ action&.named_refs&.any? { |reference| reference.fetch(:index) >= length } || false
138
+ end
139
+
140
+ # @rbs (IR::Grammar grammar, IR::Production production) -> String
141
+ def production_name(grammar, production)
142
+ lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
143
+ rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
144
+ "#{lhs} -> #{rhs.join(' ')}"
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Impact
6
+ # Maps propagated grammar symbols to parser states and conflict changes.
7
+ class AutomatonImpact
8
+ # @rbs @automaton: IR::Automaton
9
+ # @rbs @symbol_ids: Array[Integer]
10
+ # @rbs @production_ids: Array[Integer]
11
+ attr_reader :affected_states #: Array[Integer]
12
+ attr_reader :production_ids #: Array[Integer]
13
+ attr_reader :conflict_states #: Array[Integer]
14
+
15
+ # @rbs (IR::Automaton automaton, Array[Integer]) -> void
16
+ def initialize(automaton, symbol_ids)
17
+ @automaton = automaton
18
+ @symbol_ids = symbol_ids.uniq.sort #: Array[Integer]
19
+ @production_ids = affected_productions
20
+ @affected_states = affected_state_ids
21
+ @conflict_states = conflict_state_ids
22
+ freeze
23
+ end
24
+
25
+ # @rbs (Hash[Symbol, Object?] diff) -> Hash[Symbol, Object?]
26
+ def conflict_changes(diff)
27
+ diff.fetch(:conflicts) #: Hash[Symbol, Object?]
28
+ end
29
+
30
+ # @rbs () -> Hash[Symbol, Object?]
31
+ def to_h
32
+ { states: @affected_states, productions: @production_ids, conflict_states: @conflict_states }
33
+ end
34
+
35
+ private
36
+
37
+ # @rbs () -> Array[Integer]
38
+ def affected_productions
39
+ @automaton.grammar.productions.filter_map do |production|
40
+ production.id if @symbol_ids.include?(production.lhs) || production.rhs.any? { |id| @symbol_ids.include?(id) }
41
+ end.sort
42
+ end
43
+
44
+ # @rbs () -> Array[Integer]
45
+ def affected_state_ids
46
+ @automaton.states.filter_map do |state|
47
+ state.id if state.items.any? { |item| @production_ids.include?(item.production) }
48
+ end.sort
49
+ end
50
+
51
+ # @rbs () -> Array[Integer]
52
+ def conflict_state_ids
53
+ @automaton.states.filter_map { |state| state.id unless state.conflicts.empty? }.sort
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Impact
6
+ # Selects separated runtime coverage reports for affected productions.
7
+ class CoverageImpact
8
+ attr_reader :status #: String
9
+ attr_reader :reports #: Array[Hash[Symbol, Object?]]
10
+ attr_reader :warnings #: Array[String]
11
+
12
+ # @rbs (IR::Automaton automaton, Array[Integer], Array[[String, Coverage::Report]]) -> void
13
+ def initialize(automaton, production_ids, reports)
14
+ @automaton = automaton
15
+ @production_ids = production_ids.sort
16
+ @warnings = []
17
+ @reports = select_reports(reports)
18
+ @status = coverage_status(reports)
19
+ freeze
20
+ end
21
+
22
+ # @rbs () -> Hash[Symbol, Object?]
23
+ def to_h
24
+ { status: @status, reports: @reports }
25
+ end
26
+
27
+ private
28
+
29
+ # @rbs (Array[[String, Coverage::Report]]) -> Array[Hash[Symbol, Object?]]
30
+ def select_reports(reports)
31
+ selected = reports.filter_map do |path, report|
32
+ unless report.grammar_digest == @automaton.grammar_digest
33
+ @warnings << "#{path}: coverage grammar digest does not match the analysis input; report ignored"
34
+ next
35
+ end
36
+
37
+ hits = report.production_hits.keys & @production_ids
38
+ next if hits.empty?
39
+
40
+ { path: path, productions: hits.sort }
41
+ end
42
+ selected.sort_by { |entry| entry.fetch(:path) }
43
+ end
44
+
45
+ # @rbs (Array[[String, Coverage::Report]]) -> String
46
+ def coverage_status(reports)
47
+ return "not_requested" if reports.empty?
48
+
49
+ @reports.empty? ? "unmatched" : "matched"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "../analysis"
5
+
6
+ module Ibex
7
+ module Impact
8
+ # A grammar dependency edge with enough origin information for a witness.
9
+ class Edge
10
+ attr_reader :source #: Integer
11
+ attr_reader :target #: Integer
12
+ attr_reader :kind #: Symbol
13
+ attr_reader :production #: Integer?
14
+ attr_reader :position #: Integer?
15
+
16
+ # @rbs (source: Integer, target: Integer, kind: Symbol, production: Integer?, position: Integer?) -> void
17
+ def initialize(source:, target:, kind:, production: nil, position: nil)
18
+ @source = source
19
+ @target = target
20
+ @kind = kind.to_sym
21
+ @production = production
22
+ @position = position
23
+ freeze
24
+ end
25
+
26
+ # @rbs () -> Array[Integer | Symbol | nil]
27
+ def sort_key
28
+ [@source, @target, @production || -1, @position || -1, @kind]
29
+ end
30
+ end
31
+
32
+ # Combines reference, FIRST, and FOLLOW propagation dependencies.
33
+ class Graph
34
+ EDGE_KINDS = %i[reference first follow_lhs follow_first].freeze #: Array[Symbol]
35
+ KIND_ALIASES = {
36
+ all: EDGE_KINDS, reference: [:reference], first: [:first], follow: %i[follow_lhs follow_first],
37
+ follow_lhs: [:follow_lhs], follow_first: [:follow_first]
38
+ }.freeze #: Hash[Symbol, Array[Symbol]]
39
+
40
+ attr_reader :grammar #: IR::Grammar
41
+ attr_reader :sets #: Analysis::Sets
42
+
43
+ # @rbs (IR::Grammar grammar, ?sets: Analysis::Sets) -> void
44
+ def initialize(grammar, sets: nil)
45
+ @grammar = grammar
46
+ @sets = sets || Analysis::Sets.new(grammar)
47
+ @edges = build_edges
48
+ freeze_edges
49
+ end
50
+
51
+ # @rbs (?Symbol kind) -> untyped
52
+ def edges(kind = :all)
53
+ selected = KIND_ALIASES.fetch(kind.to_sym) { raise ArgumentError, "unknown impact edge kind #{kind}" }
54
+ return selected.flat_map { |name| @edges.fetch(name) }.sort_by(&:sort_key) if selected.length > 1
55
+
56
+ @edges.fetch(selected.fetch(0))
57
+ end
58
+
59
+ # @rbs (Symbol kind) -> Array[Array[Integer]]
60
+ def adjacency(kind)
61
+ result = Array.new(@grammar.symbols.length) { [] }
62
+ edges(kind).each { |edge| result[edge.source] << edge.target }
63
+ result.each(&:uniq!)
64
+ result
65
+ end
66
+
67
+ private
68
+
69
+ # @rbs () -> Hash[Symbol, Array[Edge]]
70
+ def build_edges
71
+ result = EDGE_KINDS.to_h { |kind| [kind, []] }
72
+ @grammar.productions.each do |production|
73
+ add_production_edges(result, production)
74
+ end
75
+ result.each_value { |edges| edges.sort_by!(&:sort_key) }
76
+ result
77
+ end
78
+
79
+ # @rbs (Hash[Symbol, Array[Edge]], IR::Production) -> void
80
+ def add_production_edges(result, production)
81
+ production.rhs.each_with_index do |symbol_id, position|
82
+ next unless @grammar.symbol_by_id(symbol_id)&.nonterminal?
83
+
84
+ result[:reference] << edge(symbol_id, production.lhs, :reference, production, position)
85
+ add_first_edge(result, production, symbol_id, position)
86
+ add_follow_edges(result, production, symbol_id, position)
87
+ end
88
+ end
89
+
90
+ # @rbs (Hash[Symbol, Array[Edge]], IR::Production, Integer, Integer) -> void
91
+ def add_first_edge(result, production, symbol_id, position)
92
+ prefix = production.rhs[0...position] || []
93
+ return unless @sets.sequence_nullable?(prefix)
94
+ return unless @sets.first_dependencies.fetch(symbol_id).include?(production.lhs)
95
+
96
+ result[:first] << edge(symbol_id, production.lhs, :first, production, position)
97
+ end
98
+
99
+ # @rbs (Hash[Symbol, Array[Edge]], IR::Production, Integer, Integer) -> void
100
+ def add_follow_edges(result, production, symbol_id, position)
101
+ suffix = production.rhs[(position + 1)..] || []
102
+ if @sets.sequence_nullable?(suffix) && @sets.follow_dependencies.fetch(production.lhs).include?(symbol_id)
103
+ result[:follow_lhs] << edge(production.lhs, symbol_id, :follow_lhs, production, position)
104
+ end
105
+ add_follow_first_edges(result, production, symbol_id, position)
106
+ end
107
+
108
+ # @rbs (Hash[Symbol, Array[Edge]], IR::Production, Integer, Integer) -> void
109
+ def add_follow_first_edges(result, production, symbol_id, position)
110
+ suffix = production.rhs[(position + 1)..] || []
111
+ suffix.each_with_index do |candidate, offset|
112
+ candidate_prefix = suffix[0...offset] || []
113
+ break unless @sets.sequence_nullable?(candidate_prefix)
114
+ break unless @grammar.symbol_by_id(candidate)&.nonterminal?
115
+
116
+ result[:follow_first] << edge(candidate, symbol_id, :follow_first, production, position + 1 + offset)
117
+ end
118
+ end
119
+
120
+ # @rbs (Integer source, Integer target, Symbol kind, IR::Production production, Integer position) -> Edge
121
+ def edge(source, target, kind, production, position)
122
+ Edge.new(source: source, target: target, kind: kind, production: production.id, position: position)
123
+ end
124
+
125
+ # @rbs () -> void
126
+ def freeze_edges
127
+ @edges.each_value(&:freeze)
128
+ @edges.freeze
129
+ end
130
+ end
131
+ end
132
+ end