ibex 0.1.0 → 0.3.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 (603) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -123
  3. data/docs/architecture.md +477 -31
  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 +106 -0
  12. data/docs/cst.md +237 -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 +262 -0
  40. data/docs/development.md +306 -0
  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 +26 -0
  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 +1581 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +114 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +629 -18
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/ja/bison-import.md +51 -0
  56. data/docs/ja/stable-api.md +53 -0
  57. data/docs/lexer-construction-profile.md +109 -0
  58. data/docs/lexer-migration.md +56 -0
  59. data/docs/maturity.md +128 -0
  60. data/docs/project-site-strategy.md +54 -0
  61. data/docs/racc-migration-evidence.md +45 -0
  62. data/docs/racc-migration.md +45 -6
  63. data/docs/release-readiness.md +221 -0
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +234 -0
  67. data/docs/status.md +43 -0
  68. data/docs/syntax-sessions.md +183 -0
  69. data/docs/table-artifact.md +125 -0
  70. data/docs/test-interactions.md +219 -0
  71. data/docs/verification-report.md +137 -0
  72. data/docs/verifier-trust-boundary.md +199 -0
  73. data/docs/workloads.md +132 -0
  74. data/examples/README.md +60 -0
  75. data/examples/calculator.y +44 -0
  76. data/examples/csv.y +32 -0
  77. data/examples/ini.y +70 -0
  78. data/examples/json.y +48 -0
  79. data/examples/tiny_language.y +75 -0
  80. data/lib/ibex/analysis/digraph.rb +128 -0
  81. data/lib/ibex/analysis/sets.rb +5 -4
  82. data/lib/ibex/analysis.rb +1 -0
  83. data/lib/ibex/artifact_set.rb +63 -0
  84. data/lib/ibex/bison_import/importer.rb +516 -0
  85. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  86. data/lib/ibex/bison_import.rb +200 -0
  87. data/lib/ibex/bounded_subprocess.rb +171 -0
  88. data/lib/ibex/cli/ambiguity.rb +75 -0
  89. data/lib/ibex/cli/analysis.rb +134 -0
  90. data/lib/ibex/cli/bison_import.rb +121 -0
  91. data/lib/ibex/cli/config.rb +128 -0
  92. data/lib/ibex/cli/counterexample_options.rb +6 -2
  93. data/lib/ibex/cli/coverage.rb +173 -0
  94. data/lib/ibex/cli/debug.rb +106 -0
  95. data/lib/ibex/cli/diagnostics.rb +136 -0
  96. data/lib/ibex/cli/documentation.rb +70 -0
  97. data/lib/ibex/cli/equiv.rb +169 -0
  98. data/lib/ibex/cli/error_messages.rb +184 -0
  99. data/lib/ibex/cli/explain.rb +85 -0
  100. data/lib/ibex/cli/fix.rb +203 -0
  101. data/lib/ibex/cli/formatting.rb +387 -0
  102. data/lib/ibex/cli/fuzz.rb +200 -0
  103. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  104. data/lib/ibex/cli/generation_artifacts.rb +189 -0
  105. data/lib/ibex/cli/generation_error_messages.rb +39 -0
  106. data/lib/ibex/cli/grammar_tests.rb +140 -0
  107. data/lib/ibex/cli/ir_tools.rb +128 -0
  108. data/lib/ibex/cli/lsp.rb +33 -0
  109. data/lib/ibex/cli/outputs.rb +133 -23
  110. data/lib/ibex/cli/racc_migration.rb +87 -0
  111. data/lib/ibex/cli/reduce.rb +202 -0
  112. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  113. data/lib/ibex/cli/samples.rb +118 -0
  114. data/lib/ibex/cli/verify.rb +116 -0
  115. data/lib/ibex/cli/watch.rb +104 -0
  116. data/lib/ibex/cli.rb +825 -55
  117. data/lib/ibex/codegen/action_locations.rb +103 -0
  118. data/lib/ibex/codegen/action_method_source.rb +209 -0
  119. data/lib/ibex/codegen/action_source.rb +119 -0
  120. data/lib/ibex/codegen/ambiguity.rb +174 -0
  121. data/lib/ibex/codegen/cst_metadata.rb +171 -0
  122. data/lib/ibex/codegen/documentation.rb +170 -0
  123. data/lib/ibex/codegen/explain.rb +380 -0
  124. data/lib/ibex/codegen/generated_action_abi.rb +293 -0
  125. data/lib/ibex/codegen/html.rb +94 -10
  126. data/lib/ibex/codegen/mermaid.rb +43 -0
  127. data/lib/ibex/codegen/railroad.rb +197 -0
  128. data/lib/ibex/codegen/railroad_documentation.rb +59 -0
  129. data/lib/ibex/codegen/rbs.rb +331 -2
  130. data/lib/ibex/codegen/report.rb +67 -15
  131. data/lib/ibex/codegen/ruby.rb +305 -66
  132. data/lib/ibex/codegen/ruby_actions.rb +146 -0
  133. data/lib/ibex/codegen/ruby_ast.rb +121 -0
  134. data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
  135. data/lib/ibex/codegen/ruby_lexer.rb +83 -0
  136. data/lib/ibex/codegen/ruby_syntax.rb +96 -0
  137. data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
  138. data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
  139. data/lib/ibex/codegen/symbol_labels.rb +1 -1
  140. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  141. data/lib/ibex/configuration/explanation.rb +421 -0
  142. data/lib/ibex/configuration/inspector.rb +232 -0
  143. data/lib/ibex/configuration.rb +580 -0
  144. data/lib/ibex/coverage/collector.rb +198 -0
  145. data/lib/ibex/coverage/event_stream.rb +103 -0
  146. data/lib/ibex/coverage/report.rb +267 -0
  147. data/lib/ibex/coverage/runtime_event_validator.rb +169 -0
  148. data/lib/ibex/coverage.rb +13 -0
  149. data/lib/ibex/delta_reducer.rb +99 -0
  150. data/lib/ibex/diff.rb +140 -0
  151. data/lib/ibex/equiv/machine.rb +135 -0
  152. data/lib/ibex/equiv.rb +373 -0
  153. data/lib/ibex/error_messages/parser.rb +159 -0
  154. data/lib/ibex/error_messages/parser_v2.rb +198 -0
  155. data/lib/ibex/error_messages/renderer.rb +65 -0
  156. data/lib/ibex/error_messages/sentence_search.rb +196 -0
  157. data/lib/ibex/error_messages/update.rb +169 -0
  158. data/lib/ibex/error_messages.rb +165 -0
  159. data/lib/ibex/fix.rb +557 -0
  160. data/lib/ibex/frontend/ast.rb +166 -7
  161. data/lib/ibex/frontend/bootstrap_parser.rb +40 -4
  162. data/lib/ibex/frontend/diagnostic.rb +81 -0
  163. data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
  164. data/lib/ibex/frontend/dsl.rb +66 -7
  165. data/lib/ibex/frontend/formatter.rb +453 -0
  166. data/lib/ibex/frontend/generated_parser.rb +644 -190
  167. data/lib/ibex/frontend/generated_parser_base.rb +170 -32
  168. data/lib/ibex/frontend/generated_parser_includes.rb +51 -0
  169. data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
  170. data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
  171. data/lib/ibex/frontend/generation.rb +33 -0
  172. data/lib/ibex/frontend/lexer.rb +162 -10
  173. data/lib/ibex/frontend/lexer_recovery.rb +84 -0
  174. data/lib/ibex/frontend/parser/declarations.rb +262 -11
  175. data/lib/ibex/frontend/parser/parameters.rb +82 -0
  176. data/lib/ibex/frontend/parser/rules.rb +43 -6
  177. data/lib/ibex/frontend/parser.rb +182 -4
  178. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  179. data/lib/ibex/frontend/regenerator.rb +27 -1
  180. data/lib/ibex/frontend/resolution.rb +70 -0
  181. data/lib/ibex/frontend/resolver.rb +217 -0
  182. data/lib/ibex/frontend/rule_documentation.rb +104 -0
  183. data/lib/ibex/frontend/source_cursor.rb +133 -9
  184. data/lib/ibex/frontend/source_document.rb +229 -0
  185. data/lib/ibex/frontend/source_loader.rb +150 -0
  186. data/lib/ibex/frontend/source_span.rb +89 -0
  187. data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
  188. data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
  189. data/lib/ibex/frontend/token_adapter/declaration_state.rb +244 -22
  190. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
  191. data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
  192. data/lib/ibex/frontend/token_adapter.rb +18 -3
  193. data/lib/ibex/frontend.rb +14 -2
  194. data/lib/ibex/fuzz.rb +219 -0
  195. data/lib/ibex/generation_input.rb +57 -0
  196. data/lib/ibex/generation_manifest.rb +211 -0
  197. data/lib/ibex/generation_transaction.rb +261 -0
  198. data/lib/ibex/generation_transaction_recovery.rb +109 -0
  199. data/lib/ibex/generation_transaction_validation.rb +196 -0
  200. data/lib/ibex/grammar_tests.rb +206 -0
  201. data/lib/ibex/ir/automaton_ir.rb +86 -11
  202. data/lib/ibex/ir/grammar_ir.rb +170 -26
  203. data/lib/ibex/ir/lexer_ir.rb +76 -0
  204. data/lib/ibex/ir/parser_contract.rb +135 -0
  205. data/lib/ibex/ir/serialize.rb +268 -50
  206. data/lib/ibex/ir/validator/automaton.rb +375 -0
  207. data/lib/ibex/ir/validator/base.rb +147 -0
  208. data/lib/ibex/ir/validator/grammar.rb +634 -0
  209. data/lib/ibex/ir/validator/lexer.rb +113 -0
  210. data/lib/ibex/ir/validator.rb +63 -0
  211. data/lib/ibex/ir.rb +71 -5
  212. data/lib/ibex/lalr/build_metrics.rb +71 -0
  213. data/lib/ibex/lalr/builder.rb +514 -52
  214. data/lib/ibex/lalr/conflict.rb +1 -0
  215. data/lib/ibex/lalr/conflict_search.rb +26 -9
  216. data/lib/ibex/lalr/counterexample.rb +38 -18
  217. data/lib/ibex/lalr/direct_lookaheads.rb +289 -0
  218. data/lib/ibex/lalr/goto_follows.rb +229 -0
  219. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  220. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  221. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  222. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  223. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  224. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  225. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  226. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  227. data/lib/ibex/lalr/ielr_partition.rb +166 -0
  228. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  229. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  230. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  231. data/lib/ibex/lalr/on_error_reductions.rb +74 -0
  232. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  233. data/lib/ibex/lalr.rb +59 -1
  234. data/lib/ibex/location.rb +129 -0
  235. data/lib/ibex/lsp/document_handlers.rb +68 -0
  236. data/lib/ibex/lsp/document_store.rb +264 -0
  237. data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
  238. data/lib/ibex/lsp/document_store_validation.rb +61 -0
  239. data/lib/ibex/lsp/initialization_handlers.rb +80 -0
  240. data/lib/ibex/lsp/navigation_handlers.rb +70 -0
  241. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  242. data/lib/ibex/lsp/position_codec.rb +113 -0
  243. data/lib/ibex/lsp/protocol_error.rb +33 -0
  244. data/lib/ibex/lsp/request_handlers.rb +46 -0
  245. data/lib/ibex/lsp/request_support.rb +94 -0
  246. data/lib/ibex/lsp/server.rb +148 -0
  247. data/lib/ibex/lsp/symbol_index.rb +241 -0
  248. data/lib/ibex/lsp/symbol_index_builder.rb +277 -0
  249. data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
  250. data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
  251. data/lib/ibex/lsp/symbol_occurrence.rb +23 -0
  252. data/lib/ibex/lsp/transport.rb +128 -0
  253. data/lib/ibex/lsp/workspace.rb +128 -0
  254. data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
  255. data/lib/ibex/lsp.rb +33 -0
  256. data/lib/ibex/messages/en.yml +28 -0
  257. data/lib/ibex/messages/ja.yml +28 -0
  258. data/lib/ibex/messages.rb +68 -0
  259. data/lib/ibex/metrics.rb +175 -0
  260. data/lib/ibex/normalize/declarations.rb +142 -7
  261. data/lib/ibex/normalize/diagnostics.rb +94 -24
  262. data/lib/ibex/normalize/expander.rb +64 -59
  263. data/lib/ibex/normalize/expression.rb +63 -40
  264. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  265. data/lib/ibex/normalize/inline_expansion.rb +465 -0
  266. data/lib/ibex/normalize/inline_validation.rb +174 -0
  267. data/lib/ibex/normalize/lexer.rb +131 -0
  268. data/lib/ibex/normalize/named_references.rb +60 -0
  269. data/lib/ibex/normalize/nodes.rb +46 -0
  270. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +70 -0
  271. data/lib/ibex/normalize/parameter_lowering.rb +143 -0
  272. data/lib/ibex/normalize/parameter_substitution.rb +125 -0
  273. data/lib/ibex/normalize/parameter_validation.rb +140 -0
  274. data/lib/ibex/normalize/parameters.rb +226 -0
  275. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  276. data/lib/ibex/normalize/recovery_declarations.rb +84 -0
  277. data/lib/ibex/normalize.rb +172 -17
  278. data/lib/ibex/racc_migration/checker.rb +122 -0
  279. data/lib/ibex/racc_migration/harness.rb +177 -0
  280. data/lib/ibex/racc_migration/report.rb +97 -0
  281. data/lib/ibex/racc_migration.rb +12 -0
  282. data/lib/ibex/rake_task.rb +116 -0
  283. data/lib/ibex/samples.rb +216 -0
  284. data/lib/ibex/table_artifact/builder.rb +266 -0
  285. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  286. data/lib/ibex/table_artifact/document.rb +39 -0
  287. data/lib/ibex/table_artifact/executor.rb +187 -0
  288. data/lib/ibex/table_artifact/serializer.rb +57 -0
  289. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  290. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  291. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  292. data/lib/ibex/table_artifact/validator.rb +253 -0
  293. data/lib/ibex/table_artifact.rb +88 -0
  294. data/lib/ibex/table_simulation/result.rb +55 -0
  295. data/lib/ibex/table_simulation/simulator.rb +253 -0
  296. data/lib/ibex/table_simulation/step.rb +62 -0
  297. data/lib/ibex/table_simulation/text.rb +31 -0
  298. data/lib/ibex/table_simulation.rb +13 -0
  299. data/lib/ibex/tables.rb +22 -69
  300. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  301. data/lib/ibex/verification_report/builder.rb +159 -0
  302. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  303. data/lib/ibex/verification_report/logical_path.rb +68 -0
  304. data/lib/ibex/verification_report/validator.rb +329 -0
  305. data/lib/ibex/verification_report.rb +69 -0
  306. data/lib/ibex/verify/action_correspondence.rb +138 -0
  307. data/lib/ibex/verify/language_witness.rb +295 -0
  308. data/lib/ibex/verify/reference_collection.rb +190 -0
  309. data/lib/ibex/verify/result.rb +73 -0
  310. data/lib/ibex/verify/verifier.rb +598 -0
  311. data/lib/ibex/verify.rb +15 -0
  312. data/lib/ibex/version.rb +1 -1
  313. data/lib/ibex/watch/runner.rb +181 -0
  314. data/lib/ibex/watch/source_snapshot.rb +98 -0
  315. data/lib/ibex/watch.rb +11 -0
  316. data/lib/ibex.rb +35 -1
  317. data/schema/automaton-ir-definitions.schema.json +401 -0
  318. data/schema/automaton-ir.schema.json +65 -0
  319. data/schema/benchmark-v1.schema.json +212 -0
  320. data/schema/benchmark-v2.schema.json +61 -0
  321. data/schema/bison-import-v1.schema.json +106 -0
  322. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  323. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  324. data/schema/construction-profile-v1.schema.json +779 -0
  325. data/schema/cst-v1.json +170 -0
  326. data/schema/diff-v1.schema.json +41 -0
  327. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  328. data/schema/equiv-v1.schema.json +64 -0
  329. data/schema/error-ux-review-v1.schema.json +609 -0
  330. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  331. data/schema/error-ux-round2-v1.schema.json +519 -0
  332. data/schema/error-ux-v1.schema.json +258 -0
  333. data/schema/explain-v1.schema.json +495 -0
  334. data/schema/fix-v1.schema.json +64 -0
  335. data/schema/fix-v2.schema.json +66 -0
  336. data/schema/fix-v3.schema.json +101 -0
  337. data/schema/frontend-diagnostics-v1.schema.json +154 -0
  338. data/schema/fuzz-regression-v1.schema.json +79 -0
  339. data/schema/fuzz-v1.schema.json +114 -0
  340. data/schema/generation-manifest-v1.schema.json +115 -0
  341. data/schema/grammar-ir-extensions.schema.json +734 -0
  342. data/schema/grammar-ir-foundation.schema.json +426 -0
  343. data/schema/grammar-ir.schema.json +271 -0
  344. data/schema/ielr-benchmark-v1.schema.json +85 -0
  345. data/schema/lexer-ir-v1.schema.json +215 -0
  346. data/schema/lexer-profile-v1.schema.json +491 -0
  347. data/schema/metrics-v1.schema.json +60 -0
  348. data/schema/migration-check-v1.schema.json +60 -0
  349. data/schema/performance-comparison-v1.schema.json +395 -0
  350. data/schema/public-performance-comparison-v1.schema.json +506 -0
  351. data/schema/public-performance-profile-v1.schema.json +360 -0
  352. data/schema/reduce-v1.schema.json +22 -0
  353. data/schema/reduce-v2.schema.json +63 -0
  354. data/schema/runtime-coverage-v1.schema.json +86 -0
  355. data/schema/runtime-event-v1.schema.json +308 -0
  356. data/schema/table-artifact-v1.schema.json +377 -0
  357. data/schema/table-simulation-v1.schema.json +70 -0
  358. data/schema/verification-report-v1.schema.json +222 -0
  359. data/schema/verify-v1.schema.json +42 -0
  360. data/sig/ibex/analysis/digraph.rbs +19 -0
  361. data/sig/ibex/artifact_set.rbs +37 -0
  362. data/sig/ibex/bison_import/importer.rbs +140 -0
  363. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  364. data/sig/ibex/bison_import.rbs +95 -0
  365. data/sig/ibex/bounded_subprocess.rbs +76 -0
  366. data/sig/ibex/cli/ambiguity.rbs +28 -0
  367. data/sig/ibex/cli/analysis.rbs +43 -0
  368. data/sig/ibex/cli/bison_import.rbs +32 -0
  369. data/sig/ibex/cli/config.rbs +35 -0
  370. data/sig/ibex/cli/counterexample_options.rbs +2 -0
  371. data/sig/ibex/cli/coverage.rbs +53 -0
  372. data/sig/ibex/cli/debug.rbs +28 -0
  373. data/sig/ibex/cli/diagnostics.rbs +51 -0
  374. data/sig/ibex/cli/documentation.rbs +29 -0
  375. data/sig/ibex/cli/equiv.rbs +49 -0
  376. data/sig/ibex/cli/error_messages.rbs +63 -0
  377. data/sig/ibex/cli/explain.rbs +31 -0
  378. data/sig/ibex/cli/fix.rbs +53 -0
  379. data/sig/ibex/cli/formatting.rbs +107 -0
  380. data/sig/ibex/cli/fuzz.rbs +47 -0
  381. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  382. data/sig/ibex/cli/generation_artifacts.rbs +69 -0
  383. data/sig/ibex/cli/generation_error_messages.rbs +19 -0
  384. data/sig/ibex/cli/grammar_tests.rbs +48 -0
  385. data/sig/ibex/cli/ir_tools.rbs +39 -0
  386. data/sig/ibex/cli/lsp.rbs +14 -0
  387. data/sig/ibex/cli/outputs.rbs +26 -3
  388. data/sig/ibex/cli/racc_migration.rbs +30 -0
  389. data/sig/ibex/cli/reduce.rbs +60 -0
  390. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  391. data/sig/ibex/cli/samples.rbs +38 -0
  392. data/sig/ibex/cli/verify.rbs +28 -0
  393. data/sig/ibex/cli/watch.rbs +43 -0
  394. data/sig/ibex/cli.rbs +182 -9
  395. data/sig/ibex/codegen/action_locations.rbs +45 -0
  396. data/sig/ibex/codegen/action_method_source.rbs +65 -0
  397. data/sig/ibex/codegen/action_source.rbs +50 -0
  398. data/sig/ibex/codegen/ambiguity.rbs +60 -0
  399. data/sig/ibex/codegen/cst_metadata.rbs +59 -0
  400. data/sig/ibex/codegen/documentation.rbs +50 -0
  401. data/sig/ibex/codegen/explain.rbs +105 -0
  402. data/sig/ibex/codegen/generated_action_abi.rbs +105 -0
  403. data/sig/ibex/codegen/html.rbs +18 -2
  404. data/sig/ibex/codegen/mermaid.rbs +16 -0
  405. data/sig/ibex/codegen/railroad.rbs +82 -0
  406. data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
  407. data/sig/ibex/codegen/rbs.rbs +86 -4
  408. data/sig/ibex/codegen/report.rbs +16 -8
  409. data/sig/ibex/codegen/ruby.rbs +105 -28
  410. data/sig/ibex/codegen/ruby_actions.rbs +54 -0
  411. data/sig/ibex/codegen/ruby_ast.rbs +34 -0
  412. data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
  413. data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
  414. data/sig/ibex/codegen/ruby_syntax.rbs +27 -0
  415. data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
  416. data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
  417. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  418. data/sig/ibex/configuration/explanation.rbs +195 -0
  419. data/sig/ibex/configuration/inspector.rbs +53 -0
  420. data/sig/ibex/configuration.rbs +242 -0
  421. data/sig/ibex/coverage/collector.rbs +78 -0
  422. data/sig/ibex/coverage/event_stream.rbs +44 -0
  423. data/sig/ibex/coverage/report.rbs +102 -0
  424. data/sig/ibex/coverage/runtime_event_validator.rbs +70 -0
  425. data/sig/ibex/coverage.rbs +7 -0
  426. data/sig/ibex/delta_reducer.rbs +38 -0
  427. data/sig/ibex/diff.rbs +47 -0
  428. data/sig/ibex/equiv/machine.rbs +54 -0
  429. data/sig/ibex/equiv.rbs +129 -0
  430. data/sig/ibex/error_messages/parser.rbs +58 -0
  431. data/sig/ibex/error_messages/parser_v2.rbs +67 -0
  432. data/sig/ibex/error_messages/renderer.rbs +23 -0
  433. data/sig/ibex/error_messages/sentence_search.rbs +80 -0
  434. data/sig/ibex/error_messages/update.rbs +43 -0
  435. data/sig/ibex/error_messages.rbs +85 -0
  436. data/sig/ibex/fix.rbs +138 -0
  437. data/sig/ibex/frontend/ast.rbs +237 -22
  438. data/sig/ibex/frontend/bootstrap_parser.rbs +13 -0
  439. data/sig/ibex/frontend/diagnostic.rbs +53 -0
  440. data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
  441. data/sig/ibex/frontend/dsl.rbs +33 -4
  442. data/sig/ibex/frontend/formatter.rbs +143 -0
  443. data/sig/ibex/frontend/generated_parser.rbs +228 -68
  444. data/sig/ibex/frontend/generated_parser_base.rbs +65 -12
  445. data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
  446. data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
  447. data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
  448. data/sig/ibex/frontend/generation.rbs +6 -0
  449. data/sig/ibex/frontend/lexer.rbs +49 -2
  450. data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
  451. data/sig/ibex/frontend/parser/declarations.rbs +57 -0
  452. data/sig/ibex/frontend/parser/parameters.rbs +28 -0
  453. data/sig/ibex/frontend/parser/rules.rbs +3 -0
  454. data/sig/ibex/frontend/parser.rbs +66 -0
  455. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  456. data/sig/ibex/frontend/regenerator.rbs +11 -0
  457. data/sig/ibex/frontend/resolution.rbs +34 -0
  458. data/sig/ibex/frontend/resolver.rbs +91 -0
  459. data/sig/ibex/frontend/rule_documentation.rbs +42 -0
  460. data/sig/ibex/frontend/source_cursor.rbs +38 -5
  461. data/sig/ibex/frontend/source_document.rbs +119 -0
  462. data/sig/ibex/frontend/source_loader.rbs +66 -0
  463. data/sig/ibex/frontend/source_span.rbs +56 -0
  464. data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
  465. data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
  466. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +83 -3
  467. data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
  468. data/sig/ibex/frontend/token_adapter.rbs +11 -2
  469. data/sig/ibex/frontend.rbs +1 -1
  470. data/sig/ibex/fuzz.rbs +71 -0
  471. data/sig/ibex/generation_input.rbs +37 -0
  472. data/sig/ibex/generation_manifest.rbs +70 -0
  473. data/sig/ibex/generation_transaction.rbs +82 -0
  474. data/sig/ibex/generation_transaction_recovery.rbs +36 -0
  475. data/sig/ibex/generation_transaction_validation.rbs +65 -0
  476. data/sig/ibex/grammar_tests.rbs +93 -0
  477. data/sig/ibex/ir/automaton_ir.rbs +35 -10
  478. data/sig/ibex/ir/grammar_ir.rbs +96 -22
  479. data/sig/ibex/ir/lexer_ir.rbs +57 -0
  480. data/sig/ibex/ir/parser_contract.rbs +69 -0
  481. data/sig/ibex/ir/serialize.rbs +75 -23
  482. data/sig/ibex/ir/validator/automaton.rbs +112 -0
  483. data/sig/ibex/ir/validator/base.rbs +69 -0
  484. data/sig/ibex/ir/validator/grammar.rbs +195 -0
  485. data/sig/ibex/ir/validator/lexer.rbs +37 -0
  486. data/sig/ibex/ir/validator.rbs +16 -0
  487. data/sig/ibex/ir.rbs +45 -5
  488. data/sig/ibex/lalr/build_metrics.rbs +60 -0
  489. data/sig/ibex/lalr/builder.rbs +150 -19
  490. data/sig/ibex/lalr/conflict_search.rbs +12 -5
  491. data/sig/ibex/lalr/counterexample.rbs +20 -14
  492. data/sig/ibex/lalr/direct_lookaheads.rbs +93 -0
  493. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  494. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  495. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  496. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  497. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  498. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  499. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  500. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  501. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  502. data/sig/ibex/lalr/ielr_partition.rbs +71 -0
  503. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  504. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  505. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  506. data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
  507. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  508. data/sig/ibex/lalr.rbs +17 -1
  509. data/sig/ibex/location.rbs +67 -0
  510. data/sig/ibex/lsp/document_handlers.rbs +24 -0
  511. data/sig/ibex/lsp/document_store.rbs +94 -0
  512. data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
  513. data/sig/ibex/lsp/document_store_validation.rbs +26 -0
  514. data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
  515. data/sig/ibex/lsp/navigation_handlers.rbs +33 -0
  516. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  517. data/sig/ibex/lsp/position_codec.rbs +44 -0
  518. data/sig/ibex/lsp/protocol_error.rbs +32 -0
  519. data/sig/ibex/lsp/request_handlers.rbs +26 -0
  520. data/sig/ibex/lsp/request_support.rbs +40 -0
  521. data/sig/ibex/lsp/server.rbs +55 -0
  522. data/sig/ibex/lsp/symbol_index.rbs +77 -0
  523. data/sig/ibex/lsp/symbol_index_builder.rbs +88 -0
  524. data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
  525. data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
  526. data/sig/ibex/lsp/symbol_occurrence.rbs +31 -0
  527. data/sig/ibex/lsp/transport.rbs +47 -0
  528. data/sig/ibex/lsp/workspace.rbs +42 -0
  529. data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
  530. data/sig/ibex/lsp.rbs +7 -0
  531. data/sig/ibex/messages.rbs +26 -0
  532. data/sig/ibex/metrics.rbs +50 -0
  533. data/sig/ibex/normalize/declarations.rbs +31 -0
  534. data/sig/ibex/normalize/diagnostics.rbs +13 -4
  535. data/sig/ibex/normalize/expander.rbs +22 -16
  536. data/sig/ibex/normalize/expression.rbs +11 -9
  537. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  538. data/sig/ibex/normalize/inline_expansion.rbs +129 -0
  539. data/sig/ibex/normalize/inline_validation.rbs +40 -0
  540. data/sig/ibex/normalize/lexer.rbs +37 -0
  541. data/sig/ibex/normalize/named_references.rbs +20 -0
  542. data/sig/ibex/normalize/nodes.rbs +14 -0
  543. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +33 -0
  544. data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
  545. data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
  546. data/sig/ibex/normalize/parameter_validation.rbs +44 -0
  547. data/sig/ibex/normalize/parameters.rbs +51 -0
  548. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  549. data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
  550. data/sig/ibex/normalize.rbs +129 -18
  551. data/sig/ibex/racc_migration/checker.rbs +36 -0
  552. data/sig/ibex/racc_migration/harness.rbs +16 -0
  553. data/sig/ibex/racc_migration/report.rbs +55 -0
  554. data/sig/ibex/racc_migration.rbs +8 -0
  555. data/sig/ibex/rake_task.rbs +51 -0
  556. data/sig/ibex/samples.rbs +60 -0
  557. data/sig/ibex/table_artifact/builder.rbs +93 -0
  558. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  559. data/sig/ibex/table_artifact/document.rbs +27 -0
  560. data/sig/ibex/table_artifact/executor.rbs +66 -0
  561. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  562. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  563. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  564. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  565. data/sig/ibex/table_artifact/validator.rbs +66 -0
  566. data/sig/ibex/table_artifact.rbs +32 -0
  567. data/sig/ibex/table_simulation/result.rbs +36 -0
  568. data/sig/ibex/table_simulation/simulator.rbs +98 -0
  569. data/sig/ibex/table_simulation/step.rbs +42 -0
  570. data/sig/ibex/table_simulation/text.rbs +14 -0
  571. data/sig/ibex/table_simulation.rbs +7 -0
  572. data/sig/ibex/tables.rbs +10 -35
  573. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  574. data/sig/ibex/verification_report/builder.rbs +53 -0
  575. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  576. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  577. data/sig/ibex/verification_report/validator.rbs +114 -0
  578. data/sig/ibex/verification_report.rbs +44 -0
  579. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  580. data/sig/ibex/verify/language_witness.rbs +112 -0
  581. data/sig/ibex/verify/reference_collection.rbs +63 -0
  582. data/sig/ibex/verify/result.rbs +52 -0
  583. data/sig/ibex/verify/verifier.rbs +170 -0
  584. data/sig/ibex/verify.rbs +6 -0
  585. data/sig/ibex/watch/runner.rbs +58 -0
  586. data/sig/ibex/watch/source_snapshot.rbs +46 -0
  587. data/sig/ibex/watch.rbs +7 -0
  588. data/sig/ibex.rbs +2 -0
  589. metadata +524 -19
  590. data/.rubocop.yml +0 -43
  591. data/CHANGELOG.md +0 -30
  592. data/Rakefile +0 -25
  593. data/Steepfile +0 -10
  594. data/docs/compat-notes.md +0 -37
  595. data/docs/lexer-coverage.md +0 -14
  596. data/docs/phase10-extensions.md +0 -27
  597. data/gemfiles/Gemfile +0 -7
  598. data/gemfiles/Gemfile.lock +0 -98
  599. data/lib/ibex/frontend/grammar.y +0 -156
  600. data/lib/ibex/runtime/parser.rb +0 -360
  601. data/lib/ibex/runtime.rb +0 -8
  602. data/sig/ibex/runtime/parser.rbs +0 -167
  603. data/sig/ibex/runtime.rbs +0 -6
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../fix"
7
+
8
+ module Ibex
9
+ # CLI entry point for bounded conflict-repair proposals.
10
+ # rubocop:disable Metrics/ModuleLength -- CLI option wiring and output projections share one closed contract.
11
+ module CLIFix
12
+ # @rbs!
13
+ # type fix_options = Hash[Symbol, untyped]
14
+ # private def normalize_grammar_path: (String) -> IR::Grammar
15
+ # private def activate_cli_feature: (Symbol) -> void
16
+ # private def configuration_value: (String) -> untyped
17
+ # private def set_configuration_option: (Symbol, Object?) -> void
18
+ # private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
19
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
20
+
21
+ private
22
+
23
+ # @rbs (Array[String] arguments) -> Integer
24
+ def run_fix_command(arguments)
25
+ settings = fix_options(arguments)
26
+ if settings[:help]
27
+ @stdout.puts(settings.fetch(:help))
28
+ return 0
29
+ end
30
+ paths = settings.fetch(:paths)
31
+ raise Ibex::Error, "(fix):1:1: fix requires exactly one grammar source file" unless paths.length == 1
32
+
33
+ path = paths.fetch(0)
34
+ source, fixer = prepare_fixer(path, settings)
35
+ report = fixer.run
36
+ apply_fix!(path, source, report, fixer, settings.fetch(:apply)) if settings[:apply]
37
+ write_fix_report(report, settings.fetch(:format))
38
+ proposals = report.fetch(:proposals) #: Array[Hash[Symbol, untyped]]
39
+ proposals.empty? ? 1 : 0
40
+ rescue Fix::BudgetExceeded => e
41
+ report = { ibex_report: "fix", schema_version: Fix::SCHEMA_VERSION }.merge(e.details)
42
+ write_fix_report(report, settings&.fetch(:format) || "json")
43
+ 2
44
+ end
45
+
46
+ # @rbs (String path, Hash[Symbol, untyped] settings) -> [String, Fix]
47
+ def prepare_fixer(path, settings)
48
+ source = File.binread(path)
49
+ if BisonImport.bison_source?(source)
50
+ imported = BisonImport::Importer.new(source, file: path).run
51
+ unless imported.structurally_complete?
52
+ names = imported.structural_unsupported.map(&:name).uniq.sort.join(", ")
53
+ raise Ibex::Error,
54
+ "(fix):1:1: Bison import is structurally incomplete due to: #{names}"
55
+ end
56
+ raise Ibex::Error,
57
+ "(fix):1:1: import Bison source to a canonical analysis file before requesting source repairs"
58
+ end
59
+ grammar = normalize_grammar_path(path)
60
+ grammar, algorithm, automaton = fixer_construction(grammar, settings)
61
+ message_file = settings[:messages]
62
+ messages = ErrorMessages.load(message_file) if message_file
63
+ mode = configuration_value("grammar.mode") #: Symbol
64
+ fixer = Fix.new(
65
+ source,
66
+ file: path, grammar: grammar, automaton: automaton,
67
+ algorithm: algorithm, mode: mode,
68
+ state: settings[:state], conflict_index: settings[:conflict_index],
69
+ max_candidates: settings.fetch(:max_candidates), max_builds: settings.fetch(:max_builds),
70
+ equiv_samples: settings.fetch(:equiv_samples),
71
+ equiv_max_tokens: settings.fetch(:equiv_max_tokens),
72
+ equiv_max_configurations: settings.fetch(:equiv_max_configurations),
73
+ verify_max_states: settings.fetch(:verify_max_states),
74
+ verify_max_items: settings.fetch(:verify_max_items),
75
+ messages: messages, message_file: message_file
76
+ )
77
+ [source, fixer]
78
+ end
79
+
80
+ # Keep command-local construction settings separate from reusable CLI state.
81
+ # @rbs (Array[String] arguments) -> Hash[Symbol, untyped]
82
+ def fix_options(arguments)
83
+ settings = {
84
+ paths: [], algorithm: Configuration::Registry.fetch("parser.algorithm").default,
85
+ mode: Configuration::Registry.fetch("grammar.mode").default, format: "json",
86
+ max_candidates: 32, max_builds: 32, equiv_samples: 100,
87
+ equiv_max_tokens: 8, equiv_max_configurations: 50_000,
88
+ verify_max_states: 100_000, verify_max_items: 1_000_000, configuration_explicit: []
89
+ } #: Hash[Symbol, untyped]
90
+ parser = OptionParser.new do |options|
91
+ options.banner = "Usage: ibex fix [options] GRAMMAR"
92
+ add_fix_target_options(options, settings)
93
+ add_fix_budget_options(options, settings)
94
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "current construction algorithm") do |value|
95
+ set_local_configuration_option(settings, :algorithm, value.to_sym)
96
+ end
97
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
98
+ set_local_configuration_option(settings, :mode, value.to_sym)
99
+ set_configuration_option(:mode, value.to_sym)
100
+ end
101
+ options.on("--apply[=ID]", "atomically apply one safe source proposal") do |value|
102
+ settings[:apply] = value || true
103
+ end
104
+ options.on("--messages=FILE", "measure effects on an error-message catalog") do |value|
105
+ settings[:messages] = value
106
+ end
107
+ options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
108
+ options.on("--help", "show help") { settings[:help] = options.to_s }
109
+ end
110
+ settings[:paths] = parser.parse(arguments)
111
+ settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
112
+ settings
113
+ end
114
+
115
+ # @rbs (OptionParser options, Hash[Symbol, untyped] settings) -> void
116
+ def add_fix_target_options(options, settings)
117
+ options.on("--state=N", Integer, "target state (default first unresolved conflict)") do |value|
118
+ raise OptionParser::InvalidArgument, "--state must be nonnegative" if value.negative?
119
+
120
+ settings[:state] = value
121
+ end
122
+ options.on("--conflict-index=N", Integer, "target conflict index within the state") do |value|
123
+ raise OptionParser::InvalidArgument, "--conflict-index must be nonnegative" if value.negative?
124
+
125
+ settings[:conflict_index] = value
126
+ end
127
+ end
128
+
129
+ # @rbs (OptionParser options, Hash[Symbol, untyped] settings) -> void
130
+ def add_fix_budget_options(options, settings)
131
+ {
132
+ "max-candidates" => :max_candidates,
133
+ "max-builds" => :max_builds,
134
+ "equiv-samples" => :equiv_samples,
135
+ "equiv-max-tokens" => :equiv_max_tokens,
136
+ "equiv-max-configurations" => :equiv_max_configurations,
137
+ "verify-max-states" => :verify_max_states,
138
+ "verify-max-items" => :verify_max_items
139
+ }.each do |name, key|
140
+ options.on("--#{name}=N", Integer, "positive bounded-search limit") do |value|
141
+ raise OptionParser::InvalidArgument, "--#{name} must be positive" unless value.positive?
142
+
143
+ settings[key] = value
144
+ end
145
+ end
146
+ end
147
+
148
+ # @rbs (IR::Grammar grammar, Hash[Symbol, untyped] settings) -> [IR::Grammar, Symbol, IR::Automaton]
149
+ def fixer_construction(grammar, settings)
150
+ explicit_keys = settings.fetch(:configuration_explicit) & [:algorithm]
151
+ construct_analysis_automaton(grammar, { algorithm: settings.fetch(:algorithm) }, explicit_keys)
152
+ end
153
+
154
+ # @rbs!
155
+ # private def construct_analysis_automaton: (IR::Grammar, Hash[Symbol, Object?], Array[Symbol]) ->
156
+ # [IR::Grammar, Symbol, IR::Automaton]
157
+
158
+ # @rbs (String path, String original, Hash[Symbol, untyped] report, Fix fixer, String | true selector) -> void
159
+ def apply_fix!(path, original, report, fixer, selector)
160
+ proposal = selected_fix_proposal(report.fetch(:proposals), selector)
161
+ unless proposal.fetch(:applyable)
162
+ raise Ibex::Error, "(fix):1:1: proposal #{proposal.fetch(:id)} changes invocation options, not source"
163
+ end
164
+ if File.symlink?(path) || File.stat(path).nlink > 1
165
+ raise Ibex::Error, "(fix):1:1: --apply refuses symlink aliases and files with multiple hard links"
166
+ end
167
+
168
+ replacement = fixer.sources.fetch(proposal.fetch(:id))
169
+ activate_cli_feature(:CLIFormatting)
170
+ results = [{
171
+ path: path, label: path, source: original, formatted: replacement
172
+ }]
173
+ targets = send(:formatting_targets, results)
174
+ send(:transactionally_write_formatted, targets)
175
+ report[:applied] = proposal.fetch(:id)
176
+ end
177
+
178
+ # @rbs (Array[Hash[Symbol, untyped]] proposals, String | true selector) -> Hash[Symbol, untyped]
179
+ def selected_fix_proposal(proposals, selector)
180
+ proposal = if selector == true
181
+ proposals.find { |entry| entry.fetch(:applyable) }
182
+ else
183
+ proposals.find { |entry| entry.fetch(:id) == selector }
184
+ end
185
+ proposal || raise(Ibex::Error, "(fix):1:1: no matching applyable safe proposal")
186
+ end
187
+
188
+ # @rbs (Hash[Symbol, untyped] report, String format) -> void
189
+ def write_fix_report(report, format)
190
+ if format == "json"
191
+ @stdout.puts(JSON.pretty_generate(report))
192
+ return
193
+ end
194
+
195
+ @stdout.puts("result=#{report.fetch(:result)} proposals=#{report.fetch(:proposals, []).length}")
196
+ report.fetch(:proposals, []).each do |proposal|
197
+ @stdout.puts("#{proposal.fetch(:id)} #{proposal.fetch(:description)}")
198
+ end
199
+ @stdout.puts(report.fetch(:statement, Equiv::CAVEAT))
200
+ end
201
+ end
202
+ # rubocop:enable Metrics/ModuleLength
203
+ end
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "optparse"
5
+ require_relative "../frontend"
6
+
7
+ module Ibex
8
+ # CLI coordination for deterministic, semantics-preserving grammar formatting.
9
+ # rubocop:disable Metrics/ModuleLength -- transaction lifecycle stays cohesive and auditable.
10
+ module CLIFormatting
11
+ # @rbs!
12
+ # type formatting_settings = Hash[Symbol, untyped]
13
+ # type formatting_result = {
14
+ # path: String,
15
+ # label: String,
16
+ # source: String,
17
+ # formatted: String
18
+ # }
19
+ # type formatting_target = {
20
+ # path: String,
21
+ # label: String,
22
+ # target: String,
23
+ # formatted: String,
24
+ # mode: Integer,
25
+ # directory: String,
26
+ # basename: String,
27
+ # changed: bool,
28
+ # installed: bool,
29
+ # ?stage: String,
30
+ # ?backup: String
31
+ # }
32
+ # type formatting_cleanup_result = {
33
+ # errors: Array[String],
34
+ # remaining: Array[String]
35
+ # }
36
+ # private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
37
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
38
+
39
+ private
40
+
41
+ # @rbs (Array[String] arguments) -> Integer
42
+ def run_format_command(arguments)
43
+ settings = formatting_options(arguments)
44
+ if settings[:help]
45
+ @stdout.puts(formatting_option_parser(settings))
46
+ return 0
47
+ end
48
+
49
+ validate_formatting_settings!(settings)
50
+ results, failed = prepare_formatting_results(settings)
51
+ if settings.fetch(:check)
52
+ check_status = check_formatting_results(results)
53
+ return 1 if failed
54
+
55
+ return check_status
56
+ end
57
+ return 1 if failed
58
+ return write_formatting_results(results) if settings.fetch(:write)
59
+
60
+ @stdout.write(results.fetch(0).fetch(:formatted))
61
+ 0
62
+ end
63
+
64
+ # @rbs (Array[String] arguments) -> formatting_settings
65
+ def formatting_options(arguments)
66
+ settings = {
67
+ paths: [], mode: Configuration::Registry.fetch("grammar.mode").default, check: false, write: false,
68
+ configuration_explicit: []
69
+ } #: formatting_settings
70
+ settings[:paths] = formatting_option_parser(settings).parse(arguments)
71
+ settings[:mode] = local_configuration_value(settings, "grammar.mode")
72
+ settings
73
+ end
74
+
75
+ # @rbs (formatting_settings settings) -> OptionParser
76
+ def formatting_option_parser(settings)
77
+ OptionParser.new do |options|
78
+ options.banner = "Usage: ibex fmt [--check | --write] [options] grammar.y [...]"
79
+ options.on("--check", "report files that need formatting") { settings[:check] = true }
80
+ options.on("--write", "atomically format files in place") { settings[:write] = true }
81
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
82
+ set_local_configuration_option(settings, :mode, value.to_sym)
83
+ end
84
+ options.on("--stdin-filename=FILE", "diagnostic filename for standard input") do |value|
85
+ settings[:stdin_filename] = value
86
+ end
87
+ options.on("--help", "show help") { settings[:help] = true }
88
+ end
89
+ end
90
+
91
+ # @rbs (formatting_settings settings) -> void
92
+ def validate_formatting_settings!(settings)
93
+ paths = settings.fetch(:paths)
94
+ raise Ibex::Error, "(cli):1:1: fmt requires at least one grammar file" if paths.empty?
95
+
96
+ validate_formatting_operation!(settings, paths)
97
+ validate_formatting_stdin!(settings, paths)
98
+ end
99
+
100
+ # @rbs (formatting_settings settings, Array[String] paths) -> void
101
+ def validate_formatting_operation!(settings, paths)
102
+ if settings.fetch(:check) && settings.fetch(:write)
103
+ raise Ibex::Error, "(cli):1:1: fmt --check and --write cannot be combined"
104
+ end
105
+ return if settings.fetch(:check) || settings.fetch(:write) || paths.length == 1
106
+
107
+ raise Ibex::Error, "(cli):1:1: fmt writes to stdout only for exactly one input"
108
+ end
109
+
110
+ # @rbs (formatting_settings settings, Array[String] paths) -> void
111
+ def validate_formatting_stdin!(settings, paths)
112
+ raise Ibex::Error, "(cli):1:1: fmt standard input may be specified only once" if paths.count("-") > 1
113
+
114
+ if paths.include?("-") && (settings.fetch(:check) || settings.fetch(:write))
115
+ raise Ibex::Error, "(cli):1:1: fmt --check and --write require file inputs"
116
+ end
117
+ if settings[:stdin_filename] && formatting_control_bytes?(settings.fetch(:stdin_filename))
118
+ raise Ibex::Error, "(cli):1:1: --stdin-filename must not contain control characters"
119
+ end
120
+ return unless settings[:stdin_filename] && paths != ["-"]
121
+
122
+ raise Ibex::Error, "(cli):1:1: --stdin-filename requires fmt -"
123
+ end
124
+
125
+ # @rbs (formatting_settings settings) -> [Array[formatting_result], bool]
126
+ def prepare_formatting_results(settings)
127
+ failed = false
128
+ results = [] #: Array[formatting_result]
129
+ settings.fetch(:paths).each do |path|
130
+ source = path == "-" ? @stdin.read : File.binread(path)
131
+ label = path == "-" ? settings[:stdin_filename] || "(stdin)" : formatting_label(path)
132
+ formatted = Frontend::Formatter.format(source, file: label, mode: settings.fetch(:mode))
133
+ results << { path: path, label: label, source: source, formatted: formatted }
134
+ rescue Ibex::Error, SystemCallError => e
135
+ @stderr.puts(formatting_label(e.message))
136
+ failed = true
137
+ end
138
+ [results, failed]
139
+ end
140
+
141
+ # @rbs (Array[formatting_result] results) -> Integer
142
+ def check_formatting_results(results)
143
+ changed = results.reject { |result| result.fetch(:source) == result.fetch(:formatted) }
144
+ changed.each { |result| @stderr.puts("#{result.fetch(:label)}: needs formatting") }
145
+ changed.empty? ? 0 : 1
146
+ end
147
+
148
+ # @rbs (Array[formatting_result] results) -> Integer
149
+ def write_formatting_results(results)
150
+ targets = formatting_targets(results)
151
+ validate_unique_formatting_targets!(targets)
152
+ targets = targets.select { |target| target.fetch(:changed) }
153
+ return 0 if targets.empty?
154
+
155
+ transactionally_write_formatted(targets)
156
+ 0
157
+ end
158
+
159
+ # @rbs (Array[formatting_result] results) -> Array[formatting_target]
160
+ def formatting_targets(results)
161
+ results.map do |result|
162
+ target = File.realpath(result.fetch(:path))
163
+ {
164
+ path: result.fetch(:path), label: result.fetch(:label), target: target,
165
+ formatted: result.fetch(:formatted), mode: File.stat(target).mode & 0o7777,
166
+ directory: File.dirname(target), basename: File.basename(target),
167
+ changed: result.fetch(:source) != result.fetch(:formatted), installed: false
168
+ }
169
+ end
170
+ rescue SystemCallError => e
171
+ raise Ibex::Error, formatting_label(e.message)
172
+ end
173
+
174
+ # @rbs (Array[formatting_target] targets) -> void
175
+ def validate_unique_formatting_targets!(targets)
176
+ collision = duplicate_formatting_target_pair(targets)
177
+ return unless collision
178
+
179
+ labels = collision.map { |target| target.fetch(:label) }
180
+ raise Ibex::Error, "(cli):1:1: fmt inputs resolve to the same target: #{labels.join(', ')}"
181
+ end
182
+
183
+ # @rbs (Array[formatting_target] targets) -> [formatting_target, formatting_target]?
184
+ def duplicate_formatting_target_pair(targets)
185
+ targets.each_with_index do |left, index|
186
+ targets.drop(index + 1).each do |right|
187
+ same_path = left.fetch(:target) == right.fetch(:target)
188
+ return [left, right] if same_path || File.identical?(left.fetch(:target), right.fetch(:target))
189
+ end
190
+ end
191
+ nil
192
+ end
193
+
194
+ # @rbs (Array[formatting_target] targets) -> void
195
+ def transactionally_write_formatted(targets)
196
+ begin
197
+ stage_formatting_targets(targets)
198
+ backup_formatting_targets(targets)
199
+ sync_formatting_directories!(targets)
200
+ install_formatting_targets(targets)
201
+ sync_formatting_directories!(targets)
202
+ rescue StandardError => e
203
+ rollback_errors = rollback_formatting_targets(targets)
204
+ cleanup = cleanup_formatting_artifacts(targets, preserve_installed_backups: true)
205
+ sync_errors = sync_formatting_directories_best_effort(targets)
206
+ raise formatting_transaction_error(e, rollback_errors, cleanup, sync_errors)
207
+ end
208
+
209
+ cleanup = cleanup_formatting_artifacts(targets, preserve_installed_backups: false)
210
+ sync_errors = sync_formatting_directories_best_effort(targets)
211
+ report_formatting_cleanup_warning(cleanup, sync_errors)
212
+ end
213
+
214
+ # @rbs (Array[formatting_target] targets) -> void
215
+ def stage_formatting_targets(targets)
216
+ targets.each do |target|
217
+ temporary = Tempfile.create(
218
+ [".ibex-fmt-", ".stage"], target.fetch(:directory)
219
+ )
220
+ target[:stage] = temporary.path.dup
221
+ temporary.binmode
222
+ temporary.write(target.fetch(:formatted))
223
+ temporary.flush
224
+ temporary.fsync
225
+ temporary.chmod(target.fetch(:mode))
226
+ temporary.fsync
227
+ temporary.close
228
+ rescue StandardError
229
+ begin
230
+ temporary&.close
231
+ rescue StandardError
232
+ nil
233
+ end
234
+ raise
235
+ end
236
+ end
237
+
238
+ # @rbs (Array[formatting_target] targets) -> void
239
+ def backup_formatting_targets(targets)
240
+ targets.each do |target|
241
+ backup = "#{target.fetch(:stage)}.backup"
242
+ target[:backup] = backup
243
+ File.link(target.fetch(:target), backup)
244
+ end
245
+ end
246
+
247
+ # @rbs (Array[formatting_target] targets) -> void
248
+ def install_formatting_targets(targets)
249
+ targets.each do |target|
250
+ File.rename(target.fetch(:stage), target.fetch(:target))
251
+ target[:installed] = true
252
+ end
253
+ end
254
+
255
+ # @rbs (Array[formatting_target] targets) -> Array[String]
256
+ def rollback_formatting_targets(targets)
257
+ errors = [] #: Array[String]
258
+ targets.reverse_each do |target|
259
+ next unless target.fetch(:installed)
260
+
261
+ File.rename(target.fetch(:backup), target.fetch(:target))
262
+ target[:installed] = false
263
+ rescue StandardError => e
264
+ errors << "#{target.fetch(:label)}: #{formatting_label(e.message)}"
265
+ end
266
+ errors
267
+ end
268
+
269
+ # @rbs (Array[formatting_target] targets, preserve_installed_backups: bool) ->
270
+ # formatting_cleanup_result
271
+ def cleanup_formatting_artifacts(targets, preserve_installed_backups:)
272
+ errors = [] #: Array[String]
273
+ remaining = [] #: Array[String]
274
+ targets.each do |target|
275
+ artifacts = [target[:stage]] #: Array[String?]
276
+ backup = target[:backup]
277
+ if preserve_installed_backups && target.fetch(:installed) && backup
278
+ remaining << backup
279
+ else
280
+ artifacts << backup
281
+ end
282
+ artifacts.compact.each do |path|
283
+ error = remove_formatting_artifact(path)
284
+ next unless error
285
+
286
+ errors << error
287
+ remaining << path
288
+ end
289
+ end
290
+ { errors: errors, remaining: remaining }
291
+ end
292
+
293
+ # @rbs (String path) -> String?
294
+ def remove_formatting_artifact(path)
295
+ attempts = 0
296
+ begin
297
+ attempts += 1
298
+ File.unlink(path)
299
+ nil
300
+ rescue Errno::ENOENT
301
+ nil
302
+ rescue StandardError => e
303
+ retry if attempts < 2
304
+
305
+ "#{formatting_label(path)}: #{formatting_label(e.message)}"
306
+ end
307
+ end
308
+
309
+ # @rbs (StandardError error, Array[String] rollback_errors,
310
+ # formatting_cleanup_result cleanup, Array[String] sync_errors) -> Ibex::Error
311
+ def formatting_transaction_error(error, rollback_errors, cleanup, sync_errors)
312
+ details = ["fmt write failed: #{formatting_label(error.message)}"] #: Array[String]
313
+ details << "rollback failed: #{rollback_errors.join('; ')}" unless rollback_errors.empty?
314
+ details << "cleanup failed: #{cleanup.fetch(:errors).join('; ')}" unless cleanup.fetch(:errors).empty?
315
+ unless cleanup.fetch(:remaining).empty?
316
+ paths = cleanup.fetch(:remaining).map { |path| formatting_label(path) }
317
+ details << "preserved artifacts: #{paths.join(', ')}"
318
+ end
319
+ details << "rollback directory sync failed: #{sync_errors.join('; ')}" unless sync_errors.empty?
320
+ Ibex::Error.new(details.join("; "))
321
+ end
322
+
323
+ # @rbs (formatting_cleanup_result cleanup, Array[String] sync_errors) -> void
324
+ def report_formatting_cleanup_warning(cleanup, sync_errors)
325
+ details = cleanup.fetch(:errors).dup
326
+ details.concat(sync_errors)
327
+ unless cleanup.fetch(:remaining).empty?
328
+ paths = cleanup.fetch(:remaining).map { |path| formatting_label(path) }
329
+ details << "remaining artifacts: #{paths.join(', ')}"
330
+ end
331
+ return if details.empty?
332
+
333
+ @stderr.puts("fmt cleanup warning (formatted targets committed): #{details.join('; ')}")
334
+ end
335
+
336
+ # @rbs (Array[formatting_target] targets) -> void
337
+ def sync_formatting_directories!(targets)
338
+ errors = sync_formatting_directories(targets)
339
+ return if errors.empty?
340
+
341
+ raise Ibex::Error, "directory sync failed: #{errors.join('; ')}"
342
+ end
343
+
344
+ # @rbs (Array[formatting_target] targets) -> Array[String]
345
+ def sync_formatting_directories_best_effort(targets)
346
+ sync_formatting_directories(targets)
347
+ end
348
+
349
+ # @rbs (Array[formatting_target] targets) -> Array[String]
350
+ def sync_formatting_directories(targets)
351
+ errors = [] #: Array[String]
352
+ targets.map { |target| target.fetch(:directory) }.uniq.each do |directory|
353
+ sync_formatting_directory(directory)
354
+ rescue SystemCallError, IOError => e
355
+ errors << "#{formatting_label(directory)}: #{formatting_label(e.message)}"
356
+ end
357
+ errors
358
+ end
359
+
360
+ # @rbs (String directory) -> void
361
+ def sync_formatting_directory(directory)
362
+ File.open(directory, File::RDONLY, &:fsync)
363
+ rescue Errno::EINVAL, Errno::ENOTSUP, Errno::EBADF
364
+ nil
365
+ end
366
+
367
+ # @rbs (String value) -> String
368
+ def formatting_label(value)
369
+ escaped = String.new(encoding: Encoding::BINARY)
370
+ value.b.each_byte do |byte|
371
+ escaped << (control_byte?(byte) ? format("\\x%02X", byte) : byte)
372
+ end
373
+ escaped.force_encoding(value.encoding)
374
+ end
375
+
376
+ # @rbs (String value) -> bool
377
+ def formatting_control_bytes?(value)
378
+ value.b.each_byte.any? { |byte| control_byte?(byte) }
379
+ end
380
+
381
+ # @rbs (Integer byte) -> bool
382
+ def control_byte?(byte)
383
+ byte < 0x20 || byte == 0x7f
384
+ end
385
+ end
386
+ # rubocop:enable Metrics/ModuleLength
387
+ end