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
data/lib/ibex/fix.rb ADDED
@@ -0,0 +1,557 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "equiv"
5
+ require_relative "error_messages"
6
+ require_relative "configuration/analysis_grammar"
7
+
8
+ module Ibex
9
+ # Bounded conflict-repair candidate generation and safety evaluation.
10
+ # rubocop:disable Metrics/ClassLength -- candidate generation and its three safety gates share one target identity.
11
+ class Fix
12
+ # @rbs!
13
+ # type candidate = { category: String, description: String, source: String?, algorithm: Symbol }
14
+
15
+ SCHEMA_VERSION = 3 #: Integer
16
+ CATEGORIES = %w[
17
+ precedence_declaration precedence_override algorithm_change
18
+ mechanical_rewrite
19
+ ].freeze #: Array[String]
20
+ ADVICE_CATEGORIES = %w[expectation_declaration recovery_quality].freeze #: Array[String]
21
+ ADVICE_STATEMENT = "Advice does not eliminate the selected conflict and is not a verified repair." #: String
22
+
23
+ class BudgetExceeded < Ibex::Error
24
+ attr_reader :details #: Hash[Symbol, Object?]
25
+
26
+ # @rbs (Hash[Symbol, Object?] details) -> void
27
+ def initialize(details)
28
+ @details = IR.deep_freeze(details)
29
+ super("(fix):1:1: candidate or build budget was exhausted")
30
+ end
31
+ end
32
+
33
+ attr_reader :sources #: Hash[String, String]
34
+
35
+ # @rbs (String source, file: String, grammar: IR::Grammar, automaton: IR::Automaton, algorithm: Symbol,
36
+ # mode: Symbol, ?state: Integer?, ?conflict_index: Integer?, ?max_candidates: Integer, ?max_builds: Integer,
37
+ # ?equiv_max_tokens: Integer, ?equiv_max_configurations: Integer, ?equiv_samples: Integer,
38
+ # ?verify_max_states: Integer, ?verify_max_items: Integer,
39
+ # ?messages: ErrorMessages::Document?, ?message_file: String?) -> void
40
+ # rubocop:disable Metrics/ParameterLists
41
+ def initialize(source, file:, grammar:, automaton:, algorithm:, mode:, state: nil, conflict_index: nil,
42
+ max_candidates: 32, max_builds: 32, equiv_max_tokens: 8,
43
+ equiv_max_configurations: 50_000, equiv_samples: 100,
44
+ verify_max_states: 100_000, verify_max_items: 1_000_000,
45
+ messages: nil, message_file: nil)
46
+ { max_candidates: max_candidates, max_builds: max_builds, equiv_max_tokens: equiv_max_tokens,
47
+ equiv_max_configurations: equiv_max_configurations, equiv_samples: equiv_samples,
48
+ verify_max_states: verify_max_states, verify_max_items: verify_max_items }.each do |name, value|
49
+ raise ArgumentError, "#{name} must be positive" unless value.positive?
50
+ end
51
+
52
+ @source = source
53
+ if source.match?(/^# #{Regexp.escape(BisonImport::STRUCTURAL_STATUS_MARKER)}: incomplete$/)
54
+ raise Ibex::Error,
55
+ "(fix):1:1: cannot propose repairs for a structurally incomplete Bison import"
56
+ end
57
+ @file = file
58
+ @grammar = grammar
59
+ @automaton = automaton
60
+ @algorithm = algorithm
61
+ @mode = mode
62
+ @requested_state = state
63
+ @requested_conflict_index = conflict_index
64
+ @max_candidates = max_candidates
65
+ @max_builds = max_builds
66
+ @equiv_max_tokens = equiv_max_tokens
67
+ @equiv_max_configurations = equiv_max_configurations
68
+ @equiv_samples = equiv_samples
69
+ @verify_max_states = verify_max_states
70
+ @verify_max_items = verify_max_items
71
+ @messages = messages
72
+ @message_file = message_file
73
+ @sources = {}
74
+ @builds = 0
75
+ end
76
+ # rubocop:enable Metrics/ParameterLists
77
+
78
+ # @rbs () -> Hash[Symbol, Object?]
79
+ def run
80
+ state, index, conflict = select_target
81
+ candidates = candidate_space(conflict)
82
+ advice = advice_space(conflict)
83
+ if candidates.length > @max_candidates
84
+ details = {
85
+ result: "budget_exhausted", phase: "candidate_enumeration",
86
+ bounds: bounds, enumerated_candidates: candidates.length
87
+ }
88
+ raise BudgetExceeded, details
89
+ end
90
+
91
+ proposals = [] #: Array[Hash[Symbol, Object?]]
92
+ rejections = [] #: Array[Hash[Symbol, Object?]]
93
+ incomplete = false
94
+ candidates.each do |candidate|
95
+ outcome = evaluate_candidate(candidate, conflict)
96
+ outcome_status = outcome.fetch(:status) #: String
97
+ if outcome_status == "safe"
98
+ proposals << proposal(candidate, outcome, conflict, proposals.length + 1)
99
+ else
100
+ outcome_reason = outcome.fetch(:reason) #: String
101
+ incomplete ||= %w[equivalence_budget_exhausted verification_budget_exhausted]
102
+ .include?(outcome_reason)
103
+ rejections << rejection(candidate, outcome)
104
+ end
105
+ end
106
+ report = report_for(state, index, conflict, candidates, proposals, rejections, advice)
107
+ raise BudgetExceeded, report.merge(result: "budget_exhausted", phase: "candidate_evaluation") if incomplete
108
+
109
+ report
110
+ end
111
+
112
+ private
113
+
114
+ # @rbs () -> [IR::AutomatonState, Integer, IR::conflict]
115
+ def select_target
116
+ automaton = @automaton #: IR::Automaton
117
+ states = @requested_state ? [automaton.states.fetch(@requested_state)] : automaton.states
118
+ states.each do |state|
119
+ state.conflicts.each_with_index do |conflict, index|
120
+ next if @requested_conflict_index && index != @requested_conflict_index
121
+ next unless active_conflict?(conflict)
122
+
123
+ return [state, index, conflict]
124
+ end
125
+ end
126
+ raise Ibex::Error, "(fix):1:1: no unresolved conflict matches the requested target"
127
+ rescue IndexError
128
+ raise Ibex::Error, "(fix):1:1: requested state does not exist"
129
+ end
130
+
131
+ # @rbs (IR::conflict conflict) -> Array[candidate]
132
+ def candidate_space(conflict)
133
+ candidates = precedence_declaration_candidates(conflict)
134
+ override = precedence_override_candidate(conflict)
135
+ candidates << override if override
136
+ candidates.concat(algorithm_candidates)
137
+ rewrite = inline_candidate(conflict)
138
+ candidates << rewrite if rewrite
139
+ candidates
140
+ end
141
+
142
+ # @rbs (IR::conflict conflict) -> Array[Hash[Symbol, Object?]]
143
+ def advice_space(conflict)
144
+ [expectation_candidate(conflict), recovery_candidate(conflict)].compact.map do |candidate|
145
+ source = candidate[:source]
146
+ diff = source && source != @source ? unified_diff(@source, source) : nil
147
+ {
148
+ category: candidate.fetch(:category), description: candidate.fetch(:description),
149
+ source_change: !diff.nil?, unified_diff: diff,
150
+ statement: ADVICE_STATEMENT
151
+ }
152
+ end
153
+ end
154
+
155
+ # @rbs (IR::conflict conflict) -> Array[candidate]
156
+ def precedence_declaration_candidates(conflict)
157
+ return [] unless conflict.fetch(:type).to_sym == :shift_reduce
158
+
159
+ %w[left right nonassoc %precedence].map do |associativity|
160
+ block = "preclow\n #{associativity} #{conflict.fetch(:symbol)}\nprechigh\n"
161
+ edited = update_expectation(insert_before_rule(block), conflict)
162
+ candidate(
163
+ "precedence_declaration",
164
+ "declare #{associativity} precedence for #{conflict.fetch(:symbol)}",
165
+ source: edited
166
+ )
167
+ end
168
+ end
169
+
170
+ # @rbs (IR::conflict conflict) -> candidate?
171
+ def precedence_override_candidate(conflict)
172
+ return unless conflict.fetch(:type).to_sym == :shift_reduce
173
+
174
+ value = conflict #: IR::shift_reduce_conflict
175
+ grammar = @grammar #: IR::Grammar
176
+ production = grammar.productions.fetch(value.fetch(:reduce))
177
+ return if production.node
178
+
179
+ line = production.origin.dig(:loc, :line)
180
+ return unless line
181
+
182
+ edited = update_expectation(append_to_line(line, " = #{value.fetch(:symbol)}"), conflict)
183
+ candidate(
184
+ "precedence_override",
185
+ "override production #{production.id} precedence with #{value.fetch(:symbol)}",
186
+ source: edited
187
+ )
188
+ end
189
+
190
+ # @rbs () -> Array[candidate]
191
+ def algorithm_candidates
192
+ %i[ielr lr1].reject { |algorithm| algorithm == @algorithm }.map do |algorithm|
193
+ candidate("algorithm_change", "construct with #{algorithm}", algorithm: algorithm)
194
+ end
195
+ end
196
+
197
+ # @rbs (IR::conflict conflict) -> candidate?
198
+ def inline_candidate(conflict)
199
+ production_id = if conflict.fetch(:type).to_sym == :shift_reduce
200
+ shift_reduce = conflict #: IR::shift_reduce_conflict
201
+ shift_reduce.fetch(:reduce)
202
+ else
203
+ reduce_reduce = conflict #: IR::reduce_reduce_conflict
204
+ reduce_reduce.fetch(:reductions).first
205
+ end
206
+ grammar = @grammar #: IR::Grammar
207
+ source = @source #: String
208
+ production = grammar.productions.fetch(production_id)
209
+ lhs = grammar.symbol_by_id(production.lhs)&.name
210
+ return unless lhs && !grammar.starts.include?(lhs)
211
+
212
+ pattern = /^(\s*)#{Regexp.escape(lhs)}\s*:/
213
+ return unless source.match?(pattern)
214
+
215
+ edited = update_expectation(source.sub(pattern, "\\1%inline #{lhs}:"), conflict)
216
+ candidate("mechanical_rewrite", "inline nonterminal #{lhs}", source: edited)
217
+ end
218
+
219
+ # @rbs (IR::conflict conflict) -> candidate
220
+ def expectation_candidate(conflict)
221
+ automaton = @automaton #: IR::Automaton
222
+ directive = if conflict.fetch(:type).to_sym == :reduce_reduce
223
+ "%expect-rr #{automaton.conflict_summary.fetch(:rr)}\n"
224
+ else
225
+ "expect #{automaton.conflict_summary.fetch(:sr)}\n"
226
+ end
227
+ candidate(
228
+ "expectation_declaration", "acknowledge the current conflict count",
229
+ source: replace_or_insert_expectation(directive)
230
+ )
231
+ end
232
+
233
+ # @rbs (IR::conflict conflict) -> candidate?
234
+ def recovery_candidate(conflict)
235
+ production_id = if conflict.fetch(:type).to_sym == :shift_reduce
236
+ shift_reduce = conflict #: IR::shift_reduce_conflict
237
+ shift_reduce.fetch(:reduce)
238
+ else
239
+ reduce_reduce = conflict #: IR::reduce_reduce_conflict
240
+ reduce_reduce.fetch(:reductions).first
241
+ end
242
+ grammar = @grammar #: IR::Grammar
243
+ production = grammar.productions.fetch(production_id)
244
+ lhs = grammar.symbol_by_id(production.lhs)&.name
245
+ return unless lhs
246
+
247
+ candidate(
248
+ "recovery_quality", "prefer #{lhs} during error recovery",
249
+ source: insert_before_rule("%on_error_reduce #{lhs}\n")
250
+ )
251
+ end
252
+
253
+ # @rbs (String category, String description, ?source: String?, ?algorithm: Symbol?) -> candidate
254
+ def candidate(category, description, source: nil, algorithm: nil)
255
+ { category: category, description: description, source: source, algorithm: algorithm || @algorithm }
256
+ end
257
+
258
+ # @rbs (candidate candidate, IR::conflict target) -> Hash[Symbol, Object?]
259
+ def evaluate_candidate(candidate, target)
260
+ candidate_automaton = build_candidate(candidate)
261
+ failure = conflict_safety_failure(candidate_automaton, target)
262
+ return failure if failure
263
+
264
+ failure = verification_safety_failure(candidate_automaton)
265
+ return failure if failure
266
+
267
+ equivalence = Equiv.new(
268
+ @automaton, candidate_automaton,
269
+ sample_count: @equiv_samples, max_tokens: @equiv_max_tokens,
270
+ max_configurations: @equiv_max_configurations,
271
+ rule_map: identity_rule_map(candidate_automaton.grammar)
272
+ ).run
273
+ {
274
+ status: "safe", automaton: candidate_automaton, equivalence: equivalence,
275
+ removed_conflicts: active_conflicts(@automaton).length - active_conflicts(candidate_automaton).length
276
+ }
277
+ rescue Equiv::Difference
278
+ { status: "rejected", reason: "bounded_language_or_tree_difference" }
279
+ rescue Equiv::BudgetExceeded
280
+ { status: "rejected", reason: "equivalence_budget_exhausted" }
281
+ rescue Verify::BudgetExceeded
282
+ { status: "rejected", reason: "verification_budget_exhausted" }
283
+ rescue BudgetExceeded
284
+ raise
285
+ rescue Ibex::Error, ArgumentError => e
286
+ { status: "rejected", reason: "candidate_build_failed", message: e.message }
287
+ end
288
+
289
+ # @rbs (IR::Automaton candidate, IR::conflict target) -> Hash[Symbol, String]?
290
+ def conflict_safety_failure(candidate, target)
291
+ grammar = @grammar #: IR::Grammar
292
+ automaton = @automaton #: IR::Automaton
293
+ fingerprint = conflict_fingerprint(grammar, target)
294
+ active = active_conflicts(candidate)
295
+ original_active = active_conflicts(automaton)
296
+ return { status: "rejected", reason: "target_conflict_remains" } if
297
+ active.any? { |_state, conflict| conflict_fingerprint(candidate.grammar, conflict) == fingerprint }
298
+ return { status: "rejected", reason: "conflict_count_did_not_decrease" } unless
299
+ active.length < original_active.length
300
+
301
+ { status: "rejected", reason: "other_conflicts_increased" } if
302
+ increased_other_conflict?(candidate, fingerprint)
303
+ end
304
+
305
+ # @rbs (IR::Automaton candidate) -> Hash[Symbol, String]?
306
+ def verification_safety_failure(candidate)
307
+ verification = Verify::Verifier.new(
308
+ candidate, max_states: @verify_max_states, max_items: @verify_max_items
309
+ ).verify
310
+ return { status: "rejected", reason: "candidate_failed_verification" } unless verification.valid?
311
+ return { status: "rejected", reason: "conflict_expectation_mismatch" } unless
312
+ candidate.conflict_summary.fetch(:expectation_met)
313
+ return unless candidate.conflict_summary.key?(:rr_expectation_met) &&
314
+ !candidate.conflict_summary.fetch(:rr_expectation_met)
315
+
316
+ { status: "rejected", reason: "conflict_expectation_mismatch" }
317
+ end
318
+
319
+ # @rbs (candidate candidate) -> IR::Automaton
320
+ def build_candidate(candidate)
321
+ builds = @builds #: Integer
322
+ max_builds = @max_builds #: Integer
323
+ raise BudgetExceeded, { result: "budget_exhausted", phase: "builds", bounds: bounds } if builds >= max_builds
324
+
325
+ @builds = builds + 1
326
+ source = candidate[:source]
327
+ grammar = if source
328
+ ast = Frontend::Parser.new(source, file: @file, mode: @mode).parse
329
+ Normalizer.new(ast, mode: @mode).normalize
330
+ else
331
+ @grammar
332
+ end
333
+ grammar = Configuration::AnalysisGrammar.for_algorithm(grammar, candidate.fetch(:algorithm))
334
+ LALR::Builder.new(grammar, algorithm: candidate.fetch(:algorithm)).build
335
+ end
336
+
337
+ # @rbs (candidate candidate, Hash[Symbol, Object?] outcome, IR::conflict conflict, Integer number) ->
338
+ # Hash[Symbol, Object?]
339
+ def proposal(candidate, outcome, conflict, number)
340
+ id = format("FX%03d", number)
341
+ source = candidate[:source]
342
+ @sources[id] = source if source
343
+ candidate_automaton = outcome.fetch(:automaton) #: IR::Automaton
344
+ automaton = @automaton #: IR::Automaton
345
+ grammar = @grammar #: IR::Grammar
346
+ {
347
+ id: id, category: candidate.fetch(:category), description: candidate.fetch(:description),
348
+ algorithm: candidate.fetch(:algorithm), applyable: !source.nil?,
349
+ unified_diff: source ? unified_diff(@source, source) : nil,
350
+ eliminates: [conflict_fingerprint(grammar, conflict)],
351
+ equivalence: outcome.fetch(:equivalence),
352
+ side_effects: {
353
+ states: {
354
+ before: automaton.states.length, after: candidate_automaton.states.length,
355
+ delta: candidate_automaton.states.length - automaton.states.length
356
+ },
357
+ removed_conflicts: outcome.fetch(:removed_conflicts),
358
+ message_catalog: message_catalog_impact(candidate_automaton)
359
+ }
360
+ }
361
+ end
362
+
363
+ # @rbs (IR::Automaton candidate) -> Hash[Symbol, Object?]
364
+ def message_catalog_impact(candidate)
365
+ unless @messages
366
+ empty = [] #: Array[String]
367
+ return { status: "not_configured", moved: empty, uncovered: empty, unreachable: empty }
368
+ end
369
+
370
+ file = @message_file || "(messages)"
371
+ automaton = @automaton #: IR::Automaton
372
+ baseline = ErrorMessages.update(automaton, existing: @messages)
373
+ changed = ErrorMessages.update(candidate, existing: @messages)
374
+ {
375
+ status: "evaluated", file: file,
376
+ moved: changed.moved - baseline.moved,
377
+ uncovered: changed.uncovered - baseline.uncovered,
378
+ unreachable: changed.unreachable - baseline.unreachable
379
+ }
380
+ end
381
+
382
+ # @rbs (candidate candidate, Hash[Symbol, Object?] outcome) -> Hash[Symbol, Object?]
383
+ def rejection(candidate, outcome)
384
+ {
385
+ category: candidate.fetch(:category), description: candidate.fetch(:description),
386
+ reason: outcome.fetch(:reason), message: outcome[:message]
387
+ }
388
+ end
389
+
390
+ # @rbs (IR::AutomatonState state, Integer index, IR::conflict conflict, Array[candidate] candidates,
391
+ # Array[Hash[Symbol, Object?]] proposals, Array[Hash[Symbol, Object?]] rejections,
392
+ # Array[Hash[Symbol, Object?]] advice) -> Hash[Symbol, Object?]
393
+ def report_for(state, index, conflict, candidates, proposals, rejections, advice)
394
+ inventory = CATEGORIES.to_h do |category|
395
+ values = candidates.select { |candidate| candidate.fetch(:category) == category }
396
+ rejected = rejections.count do |entry|
397
+ record = entry #: { category: String }
398
+ record.fetch(:category) == category
399
+ end
400
+ [category, { enumerated: values.length, rejected: rejected,
401
+ safe: proposals.count do |entry|
402
+ record = entry #: { category: String }
403
+ record.fetch(:category) == category
404
+ end }]
405
+ end
406
+ {
407
+ ibex_report: "fix", schema_version: SCHEMA_VERSION,
408
+ result: proposals.empty? ? "no_safe_proposal" : "proposals_found",
409
+ target: {
410
+ id: "state-#{state.id}-conflict-#{index}", state: state.id, index: index,
411
+ type: conflict.fetch(:type), symbol: conflict.fetch(:symbol),
412
+ fingerprint: conflict_fingerprint(@grammar, conflict)
413
+ },
414
+ candidate_space: inventory, advice: advice,
415
+ proposals: proposals, rejections: rejections,
416
+ bounds: bounds,
417
+ statement: Equiv::CAVEAT
418
+ }
419
+ end
420
+
421
+ # @rbs (IR::Automaton automaton) -> Array[[IR::AutomatonState, IR::conflict]]
422
+ def active_conflicts(automaton)
423
+ automaton.states.flat_map do |state|
424
+ state.conflicts.filter_map { |conflict| [state, conflict] if active_conflict?(conflict) }
425
+ end
426
+ end
427
+
428
+ # @rbs (IR::conflict conflict) -> bool
429
+ def active_conflict?(conflict)
430
+ return true if conflict.fetch(:type).to_sym == :reduce_reduce
431
+
432
+ conflict.fetch(:resolution).fetch(:by).to_sym == :default_shift
433
+ end
434
+
435
+ # @rbs (IR::Automaton candidate, String target_fingerprint) -> bool
436
+ def increased_other_conflict?(candidate, target_fingerprint)
437
+ before = conflict_counts(@automaton)
438
+ after = conflict_counts(candidate)
439
+ after.any? do |fingerprint, count|
440
+ fingerprint != target_fingerprint && count > before.fetch(fingerprint, 0)
441
+ end
442
+ end
443
+
444
+ # @rbs (IR::Automaton automaton) -> Hash[String, Integer]
445
+ def conflict_counts(automaton)
446
+ active_conflicts(automaton).each_with_object(Hash.new(0)) do |(_state, conflict), counts|
447
+ counts[conflict_fingerprint(automaton.grammar, conflict)] += 1
448
+ end
449
+ end
450
+
451
+ # @rbs (IR::Grammar grammar, IR::conflict conflict) -> String
452
+ def conflict_fingerprint(grammar, conflict)
453
+ if conflict.fetch(:type).to_sym == :shift_reduce
454
+ shift_reduce = conflict #: IR::shift_reduce_conflict
455
+ "shift_reduce:#{shift_reduce.fetch(:symbol)}:" \
456
+ "#{production_shape(grammar, shift_reduce.fetch(:reduce))}"
457
+ else
458
+ reduce_reduce = conflict #: IR::reduce_reduce_conflict
459
+ shapes = reduce_reduce.fetch(:reductions).map { |id| production_shape(grammar, id) }.sort
460
+ "reduce_reduce:#{reduce_reduce.fetch(:symbol)}:#{shapes.join('|')}"
461
+ end
462
+ end
463
+
464
+ # @rbs (IR::Grammar grammar, Integer id) -> String
465
+ def production_shape(grammar, id)
466
+ production = grammar.productions.fetch(id)
467
+ lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
468
+ rhs = production.rhs.map { |symbol| grammar.symbol_by_id(symbol)&.name || symbol.to_s }
469
+ "#{lhs}->#{rhs.join(' ')}"
470
+ end
471
+
472
+ # @rbs (IR::Grammar candidate_grammar) -> Hash[String, String]
473
+ def identity_rule_map(candidate_grammar)
474
+ grammar = @grammar #: IR::Grammar
475
+ left = grammar.nonterminals.map(&:name)
476
+ right = candidate_grammar.nonterminals.map(&:name)
477
+ (left & right).to_h { |name| [name, name] }
478
+ end
479
+
480
+ # @rbs (String insertion) -> String
481
+ def insert_before_rule(insertion)
482
+ insert_before_rule_in(@source, insertion)
483
+ end
484
+
485
+ # @rbs (String source, String insertion) -> String
486
+ def insert_before_rule_in(source, insertion)
487
+ match = source.match(/^rule\b/)
488
+ raise Ibex::Error, "(fix):1:1: source has no rule section" unless match
489
+
490
+ offset = match.begin(0)
491
+ raise Ibex::Error, "(fix):1:1: rule section has no source offset" unless offset
492
+
493
+ source.dup.insert(offset, insertion)
494
+ end
495
+
496
+ # @rbs (Integer line_number, String suffix) -> String
497
+ def append_to_line(line_number, suffix)
498
+ source = @source #: String
499
+ lines = source.lines
500
+ line = lines.fetch(line_number - 1)
501
+ ending = line.end_with?("\n") ? "\n" : ""
502
+ lines[line_number - 1] = "#{line.delete_suffix("\n")}#{suffix}#{ending}"
503
+ lines.join
504
+ rescue IndexError
505
+ raise Ibex::Error, "(fix):1:1: production source line is outside the root file"
506
+ end
507
+
508
+ # @rbs (String directive) -> String
509
+ def replace_or_insert_expectation(directive)
510
+ pattern = directive.start_with?("%expect-rr") ? /^%expect-rr\s+\d+\s*$/ : /^expect\s+\d+\s*$/
511
+ source = @source #: String
512
+ return source.sub(pattern, directive.chomp) if source.match?(pattern)
513
+
514
+ insert_before_rule(directive)
515
+ end
516
+
517
+ # @rbs (String source, IR::conflict conflict) -> String
518
+ def update_expectation(source, conflict)
519
+ automaton = @automaton #: IR::Automaton
520
+ if conflict.fetch(:type).to_sym == :reduce_reduce
521
+ current = automaton.conflict_summary.fetch(:rr)
522
+ directive = "%expect-rr #{[current - 1, 0].max}"
523
+ pattern = /^%expect-rr\s+\d+\s*$/
524
+ else
525
+ current = automaton.conflict_summary.fetch(:sr)
526
+ directive = "expect #{[current - 1, 0].max}"
527
+ pattern = /^expect\s+\d+\s*$/
528
+ end
529
+ return source.sub(pattern, directive) if source.match?(pattern)
530
+
531
+ insert_before_rule_in(source, "#{directive}\n")
532
+ end
533
+
534
+ # @rbs (String before, String after) -> String
535
+ def unified_diff(before, after)
536
+ before_lines = before.lines
537
+ after_lines = after.lines
538
+ [
539
+ "--- #{@file}", "+++ #{@file}",
540
+ "@@ -1,#{before_lines.length} +1,#{after_lines.length} @@",
541
+ *before_lines.map { |line| "-#{line.delete_suffix("\n")}" },
542
+ *after_lines.map { |line| "+#{line.delete_suffix("\n")}" }
543
+ ].join("\n") << "\n"
544
+ end
545
+
546
+ # @rbs () -> Hash[Symbol, Integer]
547
+ def bounds
548
+ {
549
+ max_candidates: @max_candidates, max_builds: @max_builds,
550
+ equiv_samples: @equiv_samples, equiv_max_tokens: @equiv_max_tokens,
551
+ equiv_max_configurations: @equiv_max_configurations,
552
+ verify_max_states: @verify_max_states, verify_max_items: @verify_max_items
553
+ }
554
+ end
555
+ end
556
+ # rubocop:enable Metrics/ClassLength
557
+ end
@@ -24,7 +24,7 @@ module Ibex
24
24
  # @rbs!
25
25
  # type symbol_metadata = DisplayName | SemanticType
26
26
  # type declaration = Include | Tokens | Precedence | Options | Expect | ExpectRR | Start | Recovery |
27
- # OnErrorReduce | GrammarTest | Lexer | Convert | Parameter | Printer | symbol_metadata
27
+ # OnErrorReduce | GrammarTest | Lexer | Convert | Parameter | Printer | ParserConfiguration | symbol_metadata
28
28
  # type lexer_entry = LexerRule | LexerState
29
29
  # type item = SymbolReference | ParameterizedReference | InlineAction | Optional | Star | Plus | Group |
30
30
  # SeparatedList | Empty
@@ -43,13 +43,15 @@ module Ibex
43
43
  # end
44
44
 
45
45
  # Adds deterministic, recursively serializable hashes to Struct nodes.
46
- # @rbs module-self Struct[untyped]
46
+ # @rbs module-self Struct[Object?]
47
47
  module Node
48
+ # @rbs () -> Hash[Symbol, untyped]
48
49
  def to_h
49
50
  fields = each_pair.to_h { |name, value| [name, serialize(value)] }
50
51
  fields.delete(:aliases) if self.class.name.end_with?("::Tokens") && !fields[:aliases]
51
52
  if self.class.name.end_with?("::Root")
52
53
  fields.delete(:extended) unless fields[:extended]
54
+ fields.delete(:extended_loc) unless fields[:extended_loc]
53
55
  fields.delete(:cst) unless fields[:cst]
54
56
  end
55
57
  fields.delete(:node_annotation) if self.class.name.end_with?("::Alternative") && !fields[:node_annotation]
@@ -58,6 +60,7 @@ module Ibex
58
60
 
59
61
  private
60
62
 
63
+ # @rbs (untyped value) -> untyped
61
64
  def serialize(value)
62
65
  return value if value.nil?
63
66
 
@@ -78,6 +81,7 @@ module Ibex
78
81
  :loc, #: Location
79
82
  :extended, #: bool?
80
83
  :cst, #: bool?
84
+ :extended_loc, #: Location?
81
85
  keyword_init: true
82
86
  ) { include Node }
83
87
  Fragment = Struct.new(
@@ -200,6 +204,22 @@ module Ibex
200
204
  :loc, #: Location
201
205
  keyword_init: true
202
206
  ) { include Node }
207
+ ParserConfiguration = Struct.new(
208
+ :settings, #: Array[ParserSetting]
209
+ :loc, #: Location
210
+ keyword_init: true
211
+ ) { include Node }
212
+ ParserSetting = Struct.new(
213
+ :key, #: Symbol
214
+ :value, #: Symbol
215
+ :loc, #: Location
216
+ keyword_init: true
217
+ ) { include Node }
218
+ ROOT_ONLY_DECLARATION_NAMES = {
219
+ Options => "options", Expect => "expect", ExpectRR => "%expect-rr", Parameter => "%param",
220
+ Start => "start", Recovery => "%recover", OnErrorReduce => "%on_error_reduce", GrammarTest => "%test",
221
+ Lexer => "lexer", ParserConfiguration => "parser"
222
+ }.freeze #: Hash[Class, String]
203
223
  # @rbs skip
204
224
  Rule = Struct.new(
205
225
  :lhs, #: String
@@ -7,6 +7,7 @@ module Ibex
7
7
  include BootstrapParserDeclarations
8
8
  include BootstrapParserRules
9
9
  include BootstrapParserParameters
10
+ include ParserConfigurationSupport
10
11
 
11
12
  # @rbs @tokens: Array[Token]
12
13
  # @rbs @index: Integer
@@ -19,7 +20,7 @@ module Ibex
19
20
  @tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
20
21
  @index = 0
21
22
  @mode = mode
22
- @pragmas = {} #: Hash[String, bool]
23
+ @pragmas = {} #: Hash[String, Location]
23
24
  end
24
25
 
25
26
  # @rbs () -> AST::Root
@@ -29,6 +30,7 @@ module Ibex
29
30
  superclass = accept(:<) ? parse_constant_path : nil
30
31
  parse_pragmas
31
32
  declarations = parse_declarations
33
+ validate_root_parser_configuration(declarations)
32
34
  expect_keyword("rule")
33
35
  rules = parse_rules
34
36
  expect_keyword("end") unless current.type == :user_code
@@ -37,7 +39,8 @@ module Ibex
37
39
  AST::Root.new(class_name: class_name, superclass: superclass, declarations: declarations,
38
40
  rules: rules, user_code: user_code, loc: location,
39
41
  extended: @mode == :extended || @pragmas.key?("extended") || @pragmas.key?("cst"),
40
- cst: @pragmas.key?("cst"))
42
+ cst: @pragmas.key?("cst"),
43
+ extended_loc: @pragmas["extended"] || @pragmas["cst"])
41
44
  end
42
45
 
43
46
  # @rbs () -> AST::Fragment
@@ -68,17 +71,7 @@ module Ibex
68
71
 
69
72
  # @rbs (AST::declaration declaration) -> String?
70
73
  def fragment_root_declaration_name(declaration)
71
- return "options" if declaration.is_a?(AST::Options)
72
- return "expect" if declaration.is_a?(AST::Expect)
73
- return "%expect-rr" if declaration.is_a?(AST::ExpectRR)
74
- return "%param" if declaration.is_a?(AST::Parameter)
75
- return "start" if declaration.is_a?(AST::Start)
76
- return "%recover" if declaration.is_a?(AST::Recovery)
77
- return "%on_error_reduce" if declaration.is_a?(AST::OnErrorReduce)
78
- return "%test" if declaration.is_a?(AST::GrammarTest)
79
- return "lexer" if declaration.is_a?(AST::Lexer)
80
-
81
- nil
74
+ AST::ROOT_ONLY_DECLARATION_NAMES.find { |node_class, _name| declaration.is_a?(node_class) }&.last
82
75
  end
83
76
 
84
77
  # @rbs () -> String
@@ -38,7 +38,7 @@ module Ibex
38
38
  @rendered
39
39
  end
40
40
 
41
- # @rbs () -> Hash[Symbol, untyped]
41
+ # @rbs () -> Hash[Symbol, Object?]
42
42
  def to_h
43
43
  {
44
44
  code: code,