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
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "graph"
5
+
6
+ module Ibex
7
+ module Impact
8
+ # One shortest witness for a symbol reached by dependency propagation.
9
+ class Node
10
+ attr_reader :symbol #: Integer
11
+ attr_reader :distance #: Integer
12
+ attr_reader :witness #: Array[Edge]
13
+ attr_reader :kind #: Symbol
14
+ attr_reader :component #: Array[Integer]
15
+
16
+ # @rbs (symbol: Integer, distance: Integer, witness: Array[Edge], kind: Symbol, component: Array[Integer]) -> void
17
+ def initialize(symbol:, distance:, witness:, kind:, component:)
18
+ @symbol = symbol
19
+ @distance = distance
20
+ @witness = witness.freeze
21
+ @kind = kind
22
+ @component = component.freeze
23
+ freeze
24
+ end
25
+ end
26
+
27
+ # Performs deterministic forward propagation over a dependency graph.
28
+ class Propagation
29
+ # @rbs @graph: Graph
30
+ # @rbs (Graph graph) -> void
31
+ def initialize(graph)
32
+ @graph = graph
33
+ end
34
+
35
+ # @rbs (Array[Integer] seeds, Symbol kind, ?max_depth: Integer?) -> Hash[Integer, Node]
36
+ def propagate(seeds, kind = :all, max_depth: nil)
37
+ validate_depth(max_depth)
38
+ selected = normalize_seeds(seeds)
39
+ adjacency = @graph.adjacency(kind)
40
+ components = Analysis::Digraph.send(:strongly_connected_components, adjacency)
41
+ component_for = component_index(components, adjacency.length)
42
+ component_edges = component_adjacency(adjacency, component_for, components.length)
43
+ component_nodes = traverse_components(selected, component_for, component_edges, max_depth)
44
+ witnesses = symbol_witnesses(selected, adjacency, kind)
45
+ build_nodes(component_nodes, components, kind, witnesses)
46
+ end
47
+
48
+ alias call propagate
49
+
50
+ private
51
+
52
+ # @rbs (Integer?) -> void
53
+ def validate_depth(max_depth)
54
+ return if max_depth.nil? || (max_depth.is_a?(Integer) && max_depth >= 0)
55
+
56
+ raise ArgumentError, "impact depth must be a non-negative integer"
57
+ end
58
+
59
+ # @rbs (Array[Integer]) -> Array[Integer]
60
+ def normalize_seeds(seeds)
61
+ seeds.uniq.sort.each do |id|
62
+ unless @graph.grammar.symbol_by_id(id)
63
+ raise ArgumentError,
64
+ "impact seed #{id.inspect} is not a grammar symbol"
65
+ end
66
+ end
67
+ end
68
+
69
+ # @rbs (Array[Array[Integer]], Integer) -> Array[Integer]
70
+ def component_index(components, size)
71
+ result = Array.new(size, 0) #: Array[Integer]
72
+ components.each_with_index { |members, id| members.each { |member| result[member] = id } }
73
+ result
74
+ end
75
+
76
+ # @rbs (Array[Array[Integer]], Array[Integer], Integer) -> Array[Array[Integer]]
77
+ def component_adjacency(adjacency, component_for, component_count)
78
+ result = Array.new(component_count) { [] } #: Array[Array[Integer]]
79
+ adjacency.each_with_index do |successors, source|
80
+ source_component = component_for.fetch(source)
81
+ successors.each do |target|
82
+ target_component = component_for.fetch(target)
83
+ next if source_component == target_component
84
+
85
+ result[source_component] << target_component
86
+ end
87
+ end
88
+ result.each do |successors|
89
+ successors.uniq!
90
+ successors.sort!
91
+ end
92
+ result
93
+ end
94
+
95
+ # @rbs (Array[Integer], Array[Integer], Array[Array[Integer]], Integer?) -> Hash[Integer, Integer]
96
+ def traverse_components(seeds, component_for, component_edges, max_depth)
97
+ queue = seeds.uniq.sort.map do |seed|
98
+ [component_for.fetch(seed), 0]
99
+ end #: Array[[Integer, Integer]]
100
+ result = {} #: Hash[Integer, Integer]
101
+ until queue.empty?
102
+ component, distance = queue.shift
103
+ next if result.key?(component)
104
+ next if max_depth && distance > max_depth
105
+
106
+ result[component] = distance
107
+ component_edges.fetch(component).each do |target|
108
+ queue << [target, distance + 1]
109
+ end
110
+ end
111
+ result
112
+ end
113
+
114
+ # @rbs (Array[Integer], Array[Array[Integer]], Symbol) -> Hash[Integer, Array[Edge]]
115
+ def symbol_witnesses(seeds, adjacency, kind)
116
+ edges = @graph.edges(kind).group_by { |edge| [edge.source, edge.target] }
117
+ queue = seeds.uniq.sort.map { |seed| [seed, []] } #: Array[[Integer, Array[Edge]]]
118
+ result = {} #: Hash[Integer, Array[Edge]]
119
+ until queue.empty?
120
+ symbol, witness = queue.shift
121
+ next if result.key?(symbol)
122
+
123
+ result[symbol] = witness
124
+ adjacency.fetch(symbol).each do |target|
125
+ edge = edges.fetch([symbol, target]).fetch(0)
126
+ queue << [target, witness + [edge]]
127
+ end
128
+ end
129
+ result
130
+ end
131
+
132
+ # @rbs (Hash[Integer, Integer], Array[Array[Integer]], Symbol, Hash[Integer, Array[Edge]]) -> Hash[Integer, Node]
133
+ def build_nodes(component_nodes, components, kind, witnesses)
134
+ result = {} #: Hash[Integer, Node]
135
+ component_nodes.each do |component, distance|
136
+ members = components.fetch(component).sort
137
+ members.each do |symbol|
138
+ result[symbol] = Node.new(
139
+ symbol: symbol, distance: distance, witness: witnesses.fetch(symbol), kind: kind, component: members
140
+ )
141
+ end
142
+ end
143
+ result.sort.to_h
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "digest"
5
+ require_relative "../ir/serialize"
6
+
7
+ module Ibex
8
+ module Impact
9
+ # Builds the deterministic public impact-v1 document.
10
+ class Report
11
+ # @rbs @mode: String
12
+ # @rbs @algorithm: String
13
+ # @rbs @grammar: IR::Grammar
14
+ # @rbs @before: IR::Automaton?
15
+ # @rbs @after: IR::Automaton?
16
+ # @rbs @seeds: Array[Hash[Symbol, Object?]]
17
+ # @rbs @nodes: Hash[Integer, Node]
18
+ # @rbs @symbol_kinds: Hash[Integer, Array[String]]
19
+ # @rbs @set_changes: Hash[String, Hash[Symbol, Object?]]
20
+ # @rbs @metadata_names: Array[String]
21
+ # @rbs @automaton: Hash[Symbol, Object?]
22
+ # @rbs @actions: Array[Hash[Symbol, Object?]]
23
+ # @rbs @coverage: Hash[Symbol, Object?]
24
+ # @rbs @minimum: String
25
+ # @rbs @warnings: Array[String]
26
+ # @rbs (mode: String, algorithm: String, grammar: IR::Grammar, before: IR::Automaton?, after: IR::Automaton?,
27
+ # seeds: Array[Hash[Symbol, Object?]], nodes: Hash[Integer, Node], symbol_kinds: Hash[Integer, Array[String]],
28
+ # set_changes: Hash[String, Hash[Symbol, Object?]], automaton: Hash[Symbol, Object?],
29
+ # actions: Array[Hash[Symbol, Object?]], coverage: Hash[Symbol, Object?], ?metadata_names: Array[String],
30
+ # ?minimum: String,
31
+ # ?warnings: Array[String]) -> void
32
+ # rubocop:disable Metrics/ParameterLists
33
+ def initialize(mode:, algorithm:, grammar:, before:, after:, seeds:, nodes:, symbol_kinds:, set_changes:,
34
+ automaton:,
35
+ actions:, coverage:, metadata_names: [], minimum: "info", warnings: [])
36
+ @mode = mode
37
+ @algorithm = algorithm
38
+ @grammar = grammar
39
+ @before = before
40
+ @after = after
41
+ @seeds = seeds
42
+ @nodes = nodes
43
+ @symbol_kinds = symbol_kinds
44
+ @set_changes = set_changes
45
+ @metadata_names = metadata_names.uniq.sort
46
+ @automaton = automaton #: Hash[Symbol, Object?]
47
+ @actions = actions #: Array[Hash[Symbol, Object?]]
48
+ @coverage = coverage #: Hash[Symbol, Object?]
49
+ @minimum = minimum
50
+ @warnings = warnings
51
+ end
52
+ # rubocop:enable Metrics/ParameterLists
53
+
54
+ # @rbs (?minimum: String) -> Hash[Symbol, Object?]
55
+ def to_h(minimum: @minimum)
56
+ symbols = symbol_documents(minimum)
57
+ action_records = action_documents(minimum)
58
+ {
59
+ ibex_report: "impact", schema_version: 1, mode: @mode, algorithm: @algorithm,
60
+ grammar_digest: { before: stable_grammar_digest(@before), after: stable_grammar_digest(@after) },
61
+ seeds: @seeds.map { |seed| seed.slice(:symbol, :origin, :nullable_boundary) },
62
+ symbols: symbols, automaton: @automaton, actions: action_records,
63
+ coverage: coverage_document, warnings: stable_warnings,
64
+ totals: totals(symbols, action_records, @automaton)
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ # @rbs (String minimum) -> Array[Hash[Symbol, Object?]]
71
+ def symbol_documents(minimum)
72
+ names = (@nodes.keys.filter_map { |id| @grammar.symbol_by_id(id)&.name } + @set_changes.keys + @metadata_names)
73
+ .uniq.sort
74
+ documents = names.filter_map { |name| symbol_document(name) }
75
+ documents.select { |document| Severity::RANK.fetch(severity_of(document)) >= Severity::RANK.fetch(minimum) }
76
+ end
77
+
78
+ # @rbs (String) -> Hash[Symbol, Object?]?
79
+ def symbol_document(name)
80
+ id = @grammar.symbol(name)&.id
81
+ node = id && @nodes[id]
82
+ return unless reportable_symbol?(name)
83
+
84
+ kinds = symbol_kinds_for(name, id)
85
+ kinds = ["reference"] if kinds.empty? && node
86
+ severity = document_severity(name, kinds)
87
+ {
88
+ symbol: name, severity: severity, kinds: kinds, distance: node&.distance || 0,
89
+ component: component_names(node, name),
90
+ sets: @set_changes.fetch(name, { first: empty_change, follow: empty_change, nullable: nil }),
91
+ witness: witness_documents(node&.witness || [])
92
+ }
93
+ end
94
+
95
+ # @rbs (String) -> bool
96
+ def reportable_symbol?(name)
97
+ !!(@grammar.symbol(name) || @set_changes.key?(name) || @metadata_names.include?(name))
98
+ end
99
+
100
+ # @rbs (String, Integer?) -> Array[String]
101
+ def symbol_kinds_for(name, id)
102
+ kinds = id ? (@symbol_kinds[id] || []).dup : [] #: Array[String]
103
+ kinds << "metadata" if @metadata_names.include?(name)
104
+ kinds.concat(change_kinds(name))
105
+ kinds.uniq.sort
106
+ end
107
+
108
+ # @rbs (String) -> Array[String]
109
+ def change_kinds(name)
110
+ change = @set_changes[name]
111
+ return [] unless change
112
+
113
+ kinds = [] #: Array[String]
114
+ first = change.fetch(:first, empty_change)
115
+ follow = change.fetch(:follow, empty_change)
116
+ kinds << "first" unless first.fetch(:added).empty? && first.fetch(:removed).empty?
117
+ kinds << "follow" unless follow.fetch(:added).empty? && follow.fetch(:removed).empty?
118
+ kinds << "nullable" if change.fetch(:nullable, nil)
119
+ kinds
120
+ end
121
+
122
+ # @rbs (String, Array[String]) -> String
123
+ def document_severity(name, kinds)
124
+ level = kinds.reduce("info") { |current, kind| Severity.max(current, kind_severity(kind)) }
125
+ action_severity(name, level)
126
+ end
127
+
128
+ # @rbs (Node?, String) -> Array[String]
129
+ def component_names(node, name)
130
+ return [name] unless node
131
+
132
+ node.component.sort.map { |member| @grammar.symbol_by_id(member)&.name }.compact.sort
133
+ end
134
+
135
+ # @rbs (String kind) -> String
136
+ def kind_severity(kind)
137
+ return "high" if %w[first follow nullable action_arity].include?(kind)
138
+ return "medium" if %w[reference precedence].include?(kind)
139
+ return "low" if kind == "metadata"
140
+
141
+ "info"
142
+ end
143
+
144
+ # @rbs (String name, String current) -> String
145
+ def action_severity(name, current)
146
+ findings = @actions.select { |action| action_production(action).start_with?("#{name} ->") }
147
+ findings.reduce(current) { |level, finding| Severity.max(level, severity_of(finding)) }
148
+ end
149
+
150
+ # @rbs (Hash[Symbol, Object?] record) -> String
151
+ def severity_of(record)
152
+ record.fetch(:severity) #: String
153
+ end
154
+
155
+ # @rbs () -> Hash[Symbol, Array[String]]
156
+ def empty_change
157
+ { added: [], removed: [] }
158
+ end
159
+
160
+ # @rbs (Array[Edge]) -> Array[Hash[Symbol, Object?]]
161
+ def witness_documents(witness)
162
+ witness.map do |edge|
163
+ production = edge.production && @grammar.productions[edge.production]
164
+ location = production&.origin&.fetch(:loc, nil) #: IR::location?
165
+ {
166
+ from: symbol_name(edge.source), to: symbol_name(edge.target), production: production_shape(production),
167
+ loc: stable_location(location)
168
+ }
169
+ end
170
+ end
171
+
172
+ # @rbs (Integer) -> String
173
+ def symbol_name(id)
174
+ @grammar.symbol_by_id(id)&.name || id.to_s
175
+ end
176
+
177
+ # @rbs (IR::Production?) -> String?
178
+ def production_shape(production)
179
+ return unless production
180
+
181
+ lhs = symbol_name(production.lhs)
182
+ rhs = production.rhs.map { |id| symbol_name(id) }
183
+ "#{lhs} -> #{rhs.join(' ')}"
184
+ end
185
+
186
+ # @rbs (Array[Hash[Symbol, Object?]], Array[Hash[Symbol, Object?]],
187
+ # Hash[Symbol, Object?]) -> Hash[Symbol, Integer]
188
+ def totals(symbols, actions, automaton)
189
+ counts = Severity::LEVELS.to_h { |level| [level.to_sym, 0] }
190
+ symbols.each { |item| counts[item.fetch(:severity).to_s.to_sym] += 1 }
191
+ actions.each { |item| counts[item.fetch(:severity).to_s.to_sym] += 1 }
192
+ conflicts = automaton.dig(:conflicts, :added) || [] #: Array[Hash[Symbol, Object?]]
193
+ counts[:critical] += conflicts.length
194
+ unreachable = automaton.fetch(:unreachable, []) #: Array[Integer]
195
+ counts[:critical] += unreachable.length
196
+ unreachable_nonterminals = automaton.fetch(:unreachable_nonterminals, []) #: Array[String]
197
+ counts[:critical] += unreachable_nonterminals.length
198
+ counts
199
+ end
200
+
201
+ # @rbs (String minimum) -> Array[Hash[Symbol, Object?]]
202
+ def action_documents(minimum)
203
+ documents = @actions.sort_by { |action| action_production(action) }.map do |action|
204
+ production = action_production(action)
205
+ severity = action.fetch(:severity) #: String
206
+ reason = action.fetch(:reason) #: String
207
+ context_length = action.fetch(:context_length) #: Hash[Symbol, Integer]
208
+ location = action.fetch(:loc, nil) #: IR::location?
209
+ {
210
+ production: production, severity: severity, reason: reason,
211
+ context_length: context_length, loc: stable_location(location)
212
+ }
213
+ end
214
+ documents.select { |action| Severity::RANK.fetch(severity_of(action)) >= Severity::RANK.fetch(minimum) }
215
+ end
216
+
217
+ # @rbs (Hash[Symbol, Object?] action) -> String
218
+ def action_production(action)
219
+ action.fetch(:production) #: String
220
+ end
221
+
222
+ # @rbs () -> Hash[Symbol, Object?]
223
+ def coverage_document
224
+ empty_reports = [] #: Array[Hash[Symbol, Object?]]
225
+ reports = @coverage.fetch(:reports, empty_reports) #: Array[Hash[Symbol, Object?]]
226
+ status = @coverage.fetch(:status) #: String
227
+ { status: status,
228
+ reports: reports.sort_by { |report| coverage_report_path(report) }.map do |report|
229
+ productions = report.fetch(:productions) #: Array[Integer]
230
+ { path: stable_path(coverage_report_path(report)), productions: productions.sort }
231
+ end }
232
+ end
233
+
234
+ # @rbs (Hash[Symbol, Object?] report) -> String
235
+ def coverage_report_path(report)
236
+ report.fetch(:path) #: String
237
+ end
238
+
239
+ # @rbs () -> Array[String]
240
+ def stable_warnings
241
+ empty_reports = [] #: Array[Hash[Symbol, Object?]]
242
+ paths = @coverage.fetch(:reports, empty_reports).map { |report| coverage_report_path(report) }
243
+ @warnings.map do |warning|
244
+ paths.reduce(warning) { |current, path| current.gsub(path, stable_path(path)) }
245
+ end.sort.uniq
246
+ end
247
+
248
+ # @rbs (IR::location?) -> Hash[Symbol, Integer]?
249
+ def stable_location(location)
250
+ return unless location
251
+
252
+ { line: location[:line], column: location[:column] }
253
+ end
254
+
255
+ # @rbs (IR::Automaton?) -> String?
256
+ def stable_grammar_digest(automaton)
257
+ return unless automaton
258
+
259
+ serialized = IR::Serialize.dump(automaton.grammar)
260
+ canonical = serialized.gsub(/"file":\s*"[^"]*"/, '"file": "<source>"')
261
+ canonical = canonical.gsub(/"root":\s*"[^"]*"/, '"root": null')
262
+ "sha256:#{Digest::SHA256.hexdigest(canonical)}"
263
+ end
264
+
265
+ # @rbs (String) -> String
266
+ def stable_path(path)
267
+ return path unless path.start_with?(File::SEPARATOR)
268
+
269
+ File.basename(path)
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Impact
6
+ # Converts command-line symbols and Diff rule ids into analysis seeds.
7
+ class Seeds
8
+ # @rbs @grammar: IR::Grammar
9
+ # @rbs @sets: Analysis::Sets
10
+ # @rbs @records: Array[Hash[Symbol, Object?]]
11
+ # @rbs @ids: Array[Integer]
12
+ attr_reader :ids #: Array[Integer]
13
+ attr_reader :records #: Array[Hash[Symbol, Object?]]
14
+
15
+ # @rbs (IR::Grammar grammar, Array[String] names, ?origin: String) -> void
16
+ def initialize(grammar, names, origin: "symbol")
17
+ @grammar = grammar
18
+ @sets = Analysis::Sets.new(grammar)
19
+ @records = names.flat_map { |name| resolve(name, origin) }.sort_by { |record| seed_symbol(record) }
20
+ @ids = @records.map { |record| seed_id(record) }.uniq.freeze
21
+ @records = @records.freeze
22
+ freeze
23
+ end
24
+
25
+ # @rbs (IR::Grammar grammar, Hash[Symbol, Object?] diff, ?origin: String) -> Seeds
26
+ def self.from_diff(grammar, diff, origin: "diff")
27
+ rules = diff.fetch(:rules) #: Hash[Symbol, Object?]
28
+ names = %i[added removed changed].flat_map do |section|
29
+ records = rules.fetch(section) #: Array[Hash[Symbol, Object?]]
30
+ records.map { |record| record.fetch(:id).to_s }
31
+ end.uniq.sort
32
+ new(grammar, names, origin: origin)
33
+ end
34
+
35
+ private
36
+
37
+ # @rbs (Hash[Symbol, Object?] record) -> String
38
+ def seed_symbol(record)
39
+ record.fetch(:symbol) #: String
40
+ end
41
+
42
+ # @rbs (Hash[Symbol, Object?] record) -> Integer
43
+ def seed_id(record)
44
+ record.fetch(:id) #: Integer
45
+ end
46
+
47
+ # @rbs (String name, String origin) -> Array[Hash[Symbol, Object?]]
48
+ def resolve(name, origin)
49
+ definition = @grammar.symbol(name)
50
+ raise Ibex::Error, "(impact):1:1: unknown symbol #{name}" unless definition
51
+ raise Ibex::Error, "(impact):1:1: impact seed #{name} is not a nonterminal" unless definition.nonterminal?
52
+
53
+ [{ symbol: definition.name, id: definition.id, origin: origin,
54
+ nullable_boundary: nullable_boundary?(definition.id) }]
55
+ end
56
+
57
+ # @rbs (Integer) -> bool
58
+ def nullable_boundary?(id)
59
+ return true if @sets.nullable?(id)
60
+
61
+ @grammar.productions.any? do |production|
62
+ production.rhs.include?(id) && production.rhs.any? do |symbol_id|
63
+ @grammar.symbol_by_id(symbol_id)&.nonterminal? && @sets.nullable?(symbol_id)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+
7
+ module Ibex
8
+ module Impact
9
+ # Stores only conflict identities so a baseline is stable across state ids.
10
+ class Baseline
11
+ # @rbs (String path) -> void
12
+ def initialize(path)
13
+ @path = path
14
+ end
15
+
16
+ # @rbs () -> Array[String]
17
+ def conflicts
18
+ return [] unless File.file?(@path)
19
+
20
+ value = JSON.parse(File.binread(@path))
21
+ validate_document(value)
22
+ conflicts = value.fetch("conflicts") #: Array[untyped]
23
+ unless conflicts.all? { |identity| identity.is_a?(String) && !identity.empty? }
24
+ raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: conflicts must contain non-empty strings"
25
+ end
26
+
27
+ conflicts.sort.uniq
28
+ rescue JSON::ParserError => e
29
+ raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: #{e.message}"
30
+ end
31
+
32
+ # @rbs (Object?) -> void
33
+ def validate_document(value)
34
+ unless value.is_a?(Hash) && value.keys.sort == %w[conflicts schema_version]
35
+ raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: expected schema_version and conflicts"
36
+ end
37
+ unless value.fetch("conflicts").is_a?(Array)
38
+ raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: conflicts must be an array"
39
+ end
40
+ return if value.fetch("schema_version") == 1
41
+
42
+ raise Ibex::Error, "#{@path}:1:1: unsupported impact baseline schema"
43
+ end
44
+
45
+ # @rbs (Array[String] identities) -> void
46
+ def write(identities)
47
+ File.write(@path, "#{JSON.pretty_generate({ 'schema_version' => 1, 'conflicts' => identities.sort.uniq })}\n")
48
+ end
49
+ end
50
+
51
+ # Severity ranking and CI gate evaluation for impact findings.
52
+ module Severity
53
+ LEVELS = %w[info low medium high critical].freeze #: Array[String]
54
+ FAIL_ON = %w[
55
+ new_conflict nullable_change first_change follow_change action_arity unreachable
56
+ ].freeze #: Array[String]
57
+ RANK = LEVELS.each_with_index.to_h.freeze #: Hash[String, Integer]
58
+ GATE_TO_KIND = {
59
+ "nullable_change" => "nullable", "first_change" => "first", "follow_change" => "follow"
60
+ }.freeze #: Hash[String, String]
61
+
62
+ module_function
63
+
64
+ # @rbs (String left, String right) -> String
65
+ def max(left, right)
66
+ RANK.fetch(left) >= RANK.fetch(right) ? left : right
67
+ end
68
+
69
+ # @rbs (Hash[String, Array[String]], Array[Hash[Symbol, String]]) -> Hash[String, String]
70
+ def symbols(changes, actions)
71
+ levels = Hash.new("info") #: Hash[String, String]
72
+ changes.each do |name, kinds|
73
+ kinds.each do |kind|
74
+ level = level_for_kind(kind)
75
+ levels[name] = max(levels[name], level)
76
+ end
77
+ end
78
+ actions.each do |finding|
79
+ name = finding.fetch(:production).split(" -> ").first
80
+ levels[name] = max(levels[name], finding.fetch(:severity))
81
+ end
82
+ levels
83
+ end
84
+
85
+ # @rbs (String) -> String
86
+ def level_for_kind(kind)
87
+ return "high" if %w[first follow nullable].include?(kind)
88
+ return "medium" if %w[reference precedence].include?(kind)
89
+
90
+ "low"
91
+ end
92
+
93
+ # @rbs (Hash[Symbol, Object?] report, Array[String] gates) -> bool
94
+ def fails?(report, gates)
95
+ return false if gates.empty?
96
+
97
+ return true if conflict_gate?(report, gates)
98
+ return true if unreachable_gate?(report, gates)
99
+ return true if action_gate?(report, gates)
100
+
101
+ symbol_records = report.fetch(:symbols, []) #: Array[Hash[Symbol, Object?]]
102
+ gates.any? do |gate|
103
+ kind = GATE_TO_KIND[gate]
104
+ kind && symbol_records.any? { |item| item[:kinds].is_a?(Array) && item[:kinds].include?(kind) }
105
+ end
106
+ end
107
+
108
+ # @rbs (Hash[Symbol, Object?], Array[String]) -> bool
109
+ def conflict_gate?(report, gates)
110
+ gates.include?("new_conflict") && report.dig(:automaton, :conflicts, :added)&.any?
111
+ end
112
+
113
+ # @rbs (Hash[Symbol, Object?], Array[String]) -> bool
114
+ def unreachable_gate?(report, gates)
115
+ return false unless gates.include?("unreachable")
116
+
117
+ states = report.dig(:automaton, :unreachable) || []
118
+ nonterminals = report.dig(:automaton, :unreachable_nonterminals) || []
119
+ states.any? || nonterminals.any?
120
+ end
121
+
122
+ # @rbs (Hash[Symbol, Object?], Array[String]) -> bool
123
+ def action_gate?(report, gates)
124
+ return false unless gates.include?("action_arity")
125
+
126
+ action_records = report.fetch(:actions, []) #: Array[Hash[Symbol, Object?]]
127
+ action_records.any? { |item| item[:severity] == "high" }
128
+ end
129
+
130
+ # @rbs (Array[String] gates) -> Array[String]
131
+ def validate_gates(gates)
132
+ unknown = gates - FAIL_ON
133
+ raise OptionParser::InvalidArgument, "unknown --fail-on value #{unknown.first.inspect}" unless unknown.empty?
134
+
135
+ gates.uniq
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "impact/graph"
5
+ require_relative "impact/propagation"
6
+ require_relative "impact/seeds"
7
+ require_relative "impact/automaton_impact"
8
+ require_relative "impact/action_impact"
9
+ require_relative "impact/coverage_impact"
10
+ require_relative "impact/severity"
11
+ require_relative "impact/report"
12
+
13
+ module Ibex
14
+ module Impact
15
+ end
16
+ end