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
@@ -19,6 +19,8 @@ module Ibex
19
19
  # private def same_file_target?: (String, String) -> bool
20
20
  # private def normalize_grammar_path: (String) -> IR::Grammar
21
21
  # private def record_generation_input: (String, String) -> GenerationInput
22
+ # private def select_configuration_mode: (String) -> void
23
+ # private def set_configuration_option: (Symbol, Object?) -> void
22
24
 
23
25
  private
24
26
 
@@ -61,9 +63,9 @@ module Ibex
61
63
  options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
62
64
  @options[:from] = value
63
65
  end
64
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
66
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| select_configuration_mode(value) }
65
67
  options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
66
- @options[:algorithm] = value.to_sym
68
+ set_configuration_option(:algorithm, value.to_sym)
67
69
  @options[:messages_algorithm_explicit] = true
68
70
  end
69
71
  options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
@@ -80,6 +82,7 @@ module Ibex
80
82
 
81
83
  report_status("reading #{input_path}")
82
84
  grammar = normalize_grammar_path(input_path)
85
+ grammar = activate_analysis_grammar(grammar)
83
86
  handle_grammar_warnings(grammar, input_path)
84
87
  build_automaton(grammar, input_path)
85
88
  end
@@ -95,14 +98,14 @@ module Ibex
95
98
  end
96
99
 
97
100
  grammar = if value.is_a?(IR::Grammar)
98
- value
101
+ activate_analysis_grammar(value)
99
102
  elsif value.is_a?(IR::Automaton)
100
103
  value.grammar
101
104
  else
102
105
  raise Ibex::Error, "#{input_path}:1:1: expected #{@options[:from]} input"
103
106
  end
104
107
  handle_grammar_warnings(grammar, input_path)
105
- return build_automaton(value, input_path) if value.is_a?(IR::Grammar)
108
+ return build_automaton(grammar, input_path) if value.is_a?(IR::Grammar)
106
109
 
107
110
  if value.is_a?(IR::Automaton)
108
111
  prepare_loaded_automaton(value, input_path)
@@ -173,5 +176,9 @@ module Ibex
173
176
 
174
177
  0o666 & ~File.umask
175
178
  end
179
+
180
+ # @rbs!
181
+ # private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
182
+ # ?explicit_keys: Array[Symbol]) -> IR::Grammar
176
183
  end
177
184
  end
@@ -12,6 +12,8 @@ module Ibex
12
12
  # private def input_path: (Array[String]) -> String
13
13
  # private def positive_counterexample_limit: (Integer, String) -> Integer
14
14
  # private def normalize_grammar_path: (String) -> IR::Grammar
15
+ # private def configuration_value: (String) -> untyped
16
+ # private def set_configuration_option: (Symbol, Object?) -> void
15
17
 
16
18
  private
17
19
 
@@ -22,9 +24,10 @@ module Ibex
22
24
  return print_help(parser) if @options[:help]
23
25
 
24
26
  path = input_path(remaining)
25
- grammar = normalize_grammar_path(path)
27
+ grammar = activate_analysis_grammar(normalize_grammar_path(path))
26
28
  automaton = LALR::Builder.new(
27
- grammar, algorithm: @options[:algorithm] || :lalr, entry_isolation: @options[:entry_isolation] == true
29
+ grammar, algorithm: configuration_value("parser.algorithm"),
30
+ entry_isolation: configuration_value("parser.entries") == :isolated
28
31
  ).build
29
32
  explanation = Codegen::Explain.new(
30
33
  automaton, state: @options[:explain_state], token: @options[:explain_token],
@@ -48,12 +51,14 @@ module Ibex
48
51
  @options[:explain_format] = value
49
52
  end
50
53
  options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
51
- @options[:algorithm] = value.to_sym
54
+ set_configuration_option(:algorithm, value.to_sym)
52
55
  end
53
56
  options.on("--entry-isolation", "build independent state sets for each start symbol") do
54
- @options[:entry_isolation] = true
57
+ set_configuration_option(:entry_isolation, true)
58
+ end
59
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
60
+ set_configuration_option(:mode, value.to_sym)
55
61
  end
56
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
57
62
  add_explain_search_options(options)
58
63
  options.on("--help", "show help") { @options[:help] = true }
59
64
  end
@@ -72,5 +77,9 @@ module Ibex
72
77
  )
73
78
  end
74
79
  end
80
+
81
+ # @rbs!
82
+ # private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
83
+ # ?explicit_keys: Array[Symbol]) -> IR::Grammar
75
84
  end
76
85
  end
@@ -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
@@ -9,14 +9,7 @@ module Ibex
9
9
  # rubocop:disable Metrics/ModuleLength -- transaction lifecycle stays cohesive and auditable.
10
10
  module CLIFormatting
11
11
  # @rbs!
12
- # type formatting_settings = {
13
- # paths: Array[String],
14
- # mode: Symbol,
15
- # check: bool,
16
- # write: bool,
17
- # ?stdin_filename: String,
18
- # ?help: bool
19
- # }
12
+ # type formatting_settings = Hash[Symbol, untyped]
20
13
  # type formatting_result = {
21
14
  # path: String,
22
15
  # label: String,
@@ -40,6 +33,8 @@ module Ibex
40
33
  # errors: Array[String],
41
34
  # remaining: Array[String]
42
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
43
38
 
44
39
  private
45
40
 
@@ -68,8 +63,12 @@ module Ibex
68
63
 
69
64
  # @rbs (Array[String] arguments) -> formatting_settings
70
65
  def formatting_options(arguments)
71
- settings = { paths: [], mode: :default, check: false, write: false } #: formatting_settings
66
+ settings = {
67
+ paths: [], mode: Configuration::Registry.fetch("grammar.mode").default, check: false, write: false,
68
+ configuration_explicit: []
69
+ } #: formatting_settings
72
70
  settings[:paths] = formatting_option_parser(settings).parse(arguments)
71
+ settings[:mode] = local_configuration_value(settings, "grammar.mode")
73
72
  settings
74
73
  end
75
74
 
@@ -79,7 +78,9 @@ module Ibex
79
78
  options.banner = "Usage: ibex fmt [--check | --write] [options] grammar.y [...]"
80
79
  options.on("--check", "report files that need formatting") { settings[:check] = true }
81
80
  options.on("--write", "atomically format files in place") { settings[:write] = true }
82
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
81
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
82
+ set_local_configuration_option(settings, :mode, value.to_sym)
83
+ end
83
84
  options.on("--stdin-filename=FILE", "diagnostic filename for standard input") do |value|
84
85
  settings[:stdin_filename] = value
85
86
  end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "optparse"
5
+ require "shellwords"
6
+ require_relative "../bounded_subprocess"
7
+ require_relative "../fuzz"
8
+ require_relative "fuzz_regressions"
9
+
10
+ module Ibex
11
+ # CLI entry point for bounded grammar-derived differential fuzzing.
12
+ module CLIFuzz
13
+ include CLIFuzzRegressions
14
+
15
+ # @rbs!
16
+ # private def print_help: (OptionParser) -> Integer
17
+ # private def input_path: (Array[String]) -> String
18
+ # private def normalize_grammar_path: (String) -> IR::Grammar
19
+ # private def handle_grammar_warnings: (IR::Grammar, String) -> void
20
+ # private def warning_categories: (String) -> Array[Symbol]
21
+ # private def select_configuration_mode: (String) -> void
22
+
23
+ private
24
+
25
+ # @rbs (Array[String] arguments) -> Integer
26
+ def run_fuzz_command(arguments)
27
+ parser = fuzz_option_parser
28
+ remaining = parser.parse(arguments)
29
+ return print_help(parser) if @options[:help]
30
+
31
+ path = input_path(remaining)
32
+ grammar = normalize_grammar_path(path)
33
+ handle_grammar_warnings(grammar, path)
34
+ external = external_fuzz_target
35
+ fuzzer = Fuzz.new(
36
+ grammar,
37
+ seed: @options.fetch(:fuzz_seed, 0), count: @options.fetch(:fuzz_count, 100),
38
+ max_tokens: @options.fetch(:fuzz_max_tokens, 32), max_depth: @options.fetch(:fuzz_max_depth, 16),
39
+ max_expansions: @options.fetch(:fuzz_max_expansions, Samples::DEFAULT_MAX_EXPANSIONS),
40
+ max_actions: @options.fetch(:fuzz_max_actions, Fuzz::DEFAULT_MAX_ACTIONS),
41
+ max_stack: @options.fetch(:fuzz_max_stack, Fuzz::DEFAULT_MAX_STACK),
42
+ coverage_guided: @options.fetch(:fuzz_coverage_guided, false),
43
+ path_length: @options.fetch(:fuzz_path_length, 2),
44
+ against: external&.[](0), against_description: external&.[](1)
45
+ )
46
+ write_fuzz_report(fuzzer.run)
47
+ 0
48
+ rescue Fuzz::Mismatch => e
49
+ raise unless fuzzer && path
50
+
51
+ begin
52
+ write_minimized_fuzz_mismatch(fuzzer, e, path, external)
53
+ rescue Fuzz::BudgetExceeded => budget
54
+ write_fuzz_budget_report(budget, external, phase: "reduction")
55
+ end
56
+ rescue Fuzz::BudgetExceeded => e
57
+ write_fuzz_budget_report(e, external, phase: "comparison")
58
+ end
59
+
60
+ # @rbs () -> OptionParser
61
+ def fuzz_option_parser
62
+ OptionParser.new do |options|
63
+ options.banner = "Usage: ibex fuzz [options] grammarfile"
64
+ add_fuzz_generation_options(options)
65
+ add_fuzz_execution_options(options)
66
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| select_configuration_mode(value) }
67
+ options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
68
+ @options[:warnings] = warning_categories(value)
69
+ end
70
+ options.on("--help", "show help") { @options[:help] = true }
71
+ end
72
+ end
73
+
74
+ # @rbs (OptionParser options) -> void
75
+ def add_fuzz_generation_options(options)
76
+ options.on("--count=N", Integer, "generated sentences (default 100)") do |value|
77
+ @options[:fuzz_count] = positive_fuzz_option!("count", value)
78
+ end
79
+ options.on("--seed=N", Integer, "deterministic seed (default 0)") { |value| @options[:fuzz_seed] = value }
80
+ options.on("--max-tokens=N", Integer, "maximum generated tokens") do |value|
81
+ @options[:fuzz_max_tokens] = positive_fuzz_option!("max-tokens", value)
82
+ end
83
+ options.on("--max-depth=N", Integer, "maximum random expansion depth") do |value|
84
+ @options[:fuzz_max_depth] = positive_fuzz_option!("max-depth", value)
85
+ end
86
+ options.on("--max-expansions=N", Integer, "total derivation work budget") do |value|
87
+ @options[:fuzz_max_expansions] = positive_fuzz_option!("max-expansions", value)
88
+ end
89
+ end
90
+
91
+ # @rbs (OptionParser options) -> void
92
+ def add_fuzz_execution_options(options)
93
+ options.on("--max-actions=N", Integer, "actions per simulated sentence") do |value|
94
+ @options[:fuzz_max_actions] = positive_fuzz_option!("max-actions", value)
95
+ end
96
+ options.on("--max-stack=N", Integer, "simulated parser stack depth") do |value|
97
+ @options[:fuzz_max_stack] = positive_fuzz_option!("max-stack", value)
98
+ end
99
+ options.on("--coverage-guided", "prefer uncovered production paths") do
100
+ @options[:fuzz_coverage_guided] = true
101
+ end
102
+ options.on("--path-length=N", Integer, "coverage path length: 1 or 2") do |value|
103
+ raise OptionParser::InvalidArgument, "--path-length must be 1 or 2" unless [1, 2].include?(value)
104
+
105
+ @options[:fuzz_path_length] = value
106
+ end
107
+ add_fuzz_external_options(options)
108
+ end
109
+
110
+ # @rbs (OptionParser options) -> void
111
+ def add_fuzz_external_options(options)
112
+ options.on("--against=COMMAND", "compare with an explicit JSON-token subprocess") do |value|
113
+ @options[:fuzz_against] = value
114
+ end
115
+ options.on("--against-runtime=DESCRIPTION", "required target runtime configuration for --against") do |value|
116
+ raise OptionParser::InvalidArgument, "--against-runtime must not be empty" if value.empty?
117
+
118
+ @options[:fuzz_against_runtime] = value
119
+ end
120
+ options.on("--against-timeout=SECONDS", Integer, "target timeout per sentence (default 10)") do |value|
121
+ @options[:fuzz_against_timeout] = positive_fuzz_option!("against-timeout", value)
122
+ end
123
+ options.on("--against-max-output=N", Integer, "target stdout/stderr byte budget") do |value|
124
+ @options[:fuzz_against_max_output] = positive_fuzz_option!("against-max-output", value)
125
+ end
126
+ options.on("--max-reduction-trials=N", Integer, "automatic mismatch-reduction trial budget") do |value|
127
+ @options[:fuzz_max_reduction_trials] = positive_fuzz_option!("max-reduction-trials", value)
128
+ end
129
+ options.on("--regression-dir=DIR", "saved minimized regressions (default test/fuzz/regressions)") do |value|
130
+ raise OptionParser::InvalidArgument, "--regression-dir must not be empty" if value.empty?
131
+
132
+ @options[:fuzz_regression_dir] = value
133
+ end
134
+ options.on("--[no-]save-regression", "persist minimized mismatches (default enabled)") do |value|
135
+ @options[:fuzz_save_regression] = value
136
+ end
137
+ options.on("--format=FORMAT", %w[json text], "json or text (default json)") do |value|
138
+ @options[:fuzz_format] = value
139
+ end
140
+ end
141
+
142
+ # @rbs () -> fuzz_external_target?
143
+ def external_fuzz_target
144
+ command = @options[:fuzz_against]
145
+ runtime = @options[:fuzz_against_runtime]
146
+ if command.nil?
147
+ raise OptionParser::InvalidArgument, "--against-runtime requires --against" if runtime
148
+
149
+ return
150
+ end
151
+ unless runtime
152
+ raise OptionParser::InvalidArgument,
153
+ "--against requires --against-runtime so the comparison target is reproducible"
154
+ end
155
+
156
+ arguments = Shellwords.split(command)
157
+ raise OptionParser::InvalidArgument, "--against command must not be empty" if arguments.empty?
158
+
159
+ timeout = @options.fetch(:fuzz_against_timeout, BoundedSubprocess::DEFAULT_TIMEOUT_SECONDS)
160
+ max_output = @options.fetch(:fuzz_against_max_output, BoundedSubprocess::DEFAULT_MAX_OUTPUT_BYTES)
161
+ subprocess = BoundedSubprocess.new(timeout_seconds: timeout, max_output_bytes: max_output)
162
+ runner = lambda do |tokens|
163
+ external_fuzz_outcome(subprocess, arguments, tokens, timeout, max_output)
164
+ end #: ^(Array[String]) -> Symbol
165
+ description = {
166
+ command: command, target_runtime: runtime,
167
+ timeout_seconds: timeout, max_output_bytes: max_output,
168
+ host_ruby_engine: RUBY_ENGINE, host_ruby_version: RUBY_VERSION,
169
+ host_platform: RUBY_PLATFORM
170
+ } #: fuzz_external_description
171
+ [runner, description]
172
+ end
173
+
174
+ # @rbs (BoundedSubprocess subprocess, Array[String] arguments, Array[String] tokens,
175
+ # Integer timeout, Integer max_output) -> Symbol
176
+ def external_fuzz_outcome(subprocess, arguments, tokens, timeout, max_output)
177
+ result = subprocess.run(arguments, input: JSON.generate(tokens))
178
+ if result.timed_out || result.output_limited
179
+ raise Fuzz::BudgetExceeded.new(
180
+ message: "external target exceeded its #{result.timed_out ? 'timeout' : 'output limit'}",
181
+ bounds: { timeout_seconds: timeout, max_output_bytes: max_output }
182
+ )
183
+ end
184
+ unless result.status.exited? && [0, 1].include?(result.status.exitstatus)
185
+ detail = result.stderr.empty? ? result.stdout : result.stderr
186
+ raise Ibex::Error,
187
+ "(fuzz):1:1: external target did not exit with 0 or 1: #{detail}"
188
+ end
189
+
190
+ result.status.success? ? :accepted : :error
191
+ end
192
+
193
+ # @rbs (String name, Integer value) -> Integer
194
+ def positive_fuzz_option!(name, value)
195
+ return value if value.positive?
196
+
197
+ raise OptionParser::InvalidArgument, "--#{name} must be positive"
198
+ end
199
+ end
200
+ end