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
data/lib/ibex/cli.rb CHANGED
@@ -1,39 +1,112 @@
1
1
  # frozen_string_literal: true
2
+ # rbs_inline: enabled
2
3
 
4
+ require "json"
3
5
  require "optparse"
4
- require_relative "../ibex"
6
+ require_relative "version"
7
+ require_relative "error"
8
+ require_relative "configuration"
9
+ require_relative "messages"
10
+ require_relative "artifact_set"
11
+ require_relative "generation_input"
12
+ require_relative "generation_transaction"
13
+ require_relative "frontend/generation"
14
+ require_relative "ir/grammar_ir"
15
+ require_relative "ir/lexer_ir"
16
+ require_relative "ir/serialize"
17
+ require_relative "ir/automaton_ir"
18
+ require_relative "normalize"
19
+ require_relative "analysis"
20
+ require_relative "lalr/conflict"
21
+ require_relative "lalr/on_error_reductions"
22
+ require_relative "lalr/default_reductions"
23
+ require_relative "lalr/build_metrics"
24
+ require_relative "lalr/inadequacy_report"
25
+ require_relative "lalr/direct_lookaheads"
26
+ require_relative "lalr/ielr_partition"
27
+ require_relative "lalr/builder"
28
+ require_relative "codegen/ruby"
5
29
  require_relative "cli/counterexample_options"
30
+ require_relative "cli/generation_error_messages"
31
+ require_relative "cli/generation_artifacts"
6
32
  require_relative "cli/outputs"
7
33
 
8
34
  module Ibex
35
+ CLI_FEATURE_ROOT = File.expand_path("cli", __dir__ || raise("CLI source directory is unavailable")) #: String
36
+ autoload :CLIAmbiguity, File.join(CLI_FEATURE_ROOT, "ambiguity")
37
+ autoload :CLIAnalysis, File.join(CLI_FEATURE_ROOT, "analysis")
38
+ autoload :CLIBisonImport, File.join(CLI_FEATURE_ROOT, "bison_import")
39
+ autoload :CLICoverage, File.join(CLI_FEATURE_ROOT, "coverage")
40
+ autoload :CLIDebug, File.join(CLI_FEATURE_ROOT, "debug")
41
+ autoload :CLIDiagnostics, File.join(CLI_FEATURE_ROOT, "diagnostics")
42
+ autoload :CLIDocumentation, File.join(CLI_FEATURE_ROOT, "documentation")
43
+ autoload :CLIErrorMessages, File.join(CLI_FEATURE_ROOT, "error_messages")
44
+ autoload :CLIEquiv, File.join(CLI_FEATURE_ROOT, "equiv")
45
+ autoload :CLIExplain, File.join(CLI_FEATURE_ROOT, "explain")
46
+ autoload :CLIFormatting, File.join(CLI_FEATURE_ROOT, "formatting")
47
+ autoload :CLIFix, File.join(CLI_FEATURE_ROOT, "fix")
48
+ autoload :CLIFuzz, File.join(CLI_FEATURE_ROOT, "fuzz")
49
+ autoload :CLIGrammarTests, File.join(CLI_FEATURE_ROOT, "grammar_tests")
50
+ autoload :CLIIRTools, File.join(CLI_FEATURE_ROOT, "ir_tools")
51
+ autoload :CLILSP, File.join(CLI_FEATURE_ROOT, "lsp")
52
+ autoload :CLIRaccMigration, File.join(CLI_FEATURE_ROOT, "racc_migration")
53
+ autoload :CLIReduce, File.join(CLI_FEATURE_ROOT, "reduce")
54
+ autoload :CLISamples, File.join(CLI_FEATURE_ROOT, "samples")
55
+ autoload :CLIVerify, File.join(CLI_FEATURE_ROOT, "verify")
56
+ autoload :CLIWatch, File.join(CLI_FEATURE_ROOT, "watch")
57
+
9
58
  # @rbs!
10
59
  # interface _CLIOutput
11
- # def puts: (*untyped) -> untyped
12
- # def write: (String) -> untyped
60
+ # def puts: (*Object?) -> Object?
61
+ # def write: (String) -> Object?
62
+ # end
63
+ # interface _CLIInput
64
+ # def read: () -> String
65
+ # def gets: () -> String?
13
66
  # end
14
67
  # type cli_options = {
15
68
  # emit: String,
16
69
  # mode: Symbol,
17
70
  # table: Symbol,
71
+ # ?cst_trivia: Symbol,
18
72
  # line_convert: bool,
19
73
  # ?line_convert_all: bool,
20
74
  # counterexample_max_tokens: Integer,
21
75
  # counterexample_max_configurations: Integer,
22
76
  # ?from: String,
23
77
  # ?algorithm: Symbol,
78
+ # ?suggest_ielr: bool,
79
+ # ?entry_isolation: bool,
80
+ # ?ielr_strategy: Symbol,
81
+ # ?ielr_report: bool,
82
+ # ?remove_unreachable: bool,
24
83
  # ?warnings: Array[Symbol],
25
84
  # ?output: String,
26
85
  # ?embedded: bool,
27
86
  # ?debug: bool,
28
87
  # ?verbose: bool,
29
88
  # ?rbs: String | true,
89
+ # ?action_source: String | true,
90
+ # ?manifest: String | true,
91
+ # ?watch: bool,
30
92
  # ?dot: String,
93
+ # ?mermaid: String,
31
94
  # ?html: String,
95
+ # ?railroad: String,
96
+ # ?messages: String,
97
+ # ?messages_update: String | true,
98
+ # ?messages_algorithm_explicit: bool,
99
+ # ?sample_count: Integer,
100
+ # ?sample_seed: Integer,
101
+ # ?sample_max_tokens: Integer,
102
+ # ?sample_max_depth: Integer,
103
+ # ?sample_max_expansions: Integer,
32
104
  # ?log_file: String,
33
105
  # ?executable: String,
34
106
  # ?frozen: bool,
35
107
  # ?omit_actions: bool,
36
108
  # ?superclass: String,
109
+ # ?verify_output: bool,
37
110
  # ?check_only: bool,
38
111
  # ?status: bool,
39
112
  # ?profile: bool,
@@ -41,111 +114,394 @@ module Ibex
41
114
  # ?version: bool,
42
115
  # ?runtime_version: bool,
43
116
  # ?copyright: bool,
44
- # ?help: bool
117
+ # ?help: bool,
118
+ # ?explain_state: Integer,
119
+ # ?explain_token: String,
120
+ # ?explain_format: String,
121
+ # ?check_ambiguity: bool,
122
+ # ?check_format: String,
123
+ # ?fuzz_seed: Integer,
124
+ # ?fuzz_count: Integer,
125
+ # ?fuzz_max_tokens: Integer,
126
+ # ?fuzz_max_depth: Integer,
127
+ # ?fuzz_max_expansions: Integer,
128
+ # ?fuzz_max_actions: Integer,
129
+ # ?fuzz_max_stack: Integer,
130
+ # ?fuzz_coverage_guided: bool,
131
+ # ?fuzz_path_length: Integer,
132
+ # ?fuzz_against: String,
133
+ # ?fuzz_against_runtime: String,
134
+ # ?fuzz_against_timeout: Integer,
135
+ # ?fuzz_against_max_output: Integer,
136
+ # ?fuzz_max_reduction_trials: Integer,
137
+ # ?fuzz_regression_dir: String,
138
+ # ?fuzz_save_regression: bool,
139
+ # ?fuzz_format: String,
140
+ # ?reduce_command: String,
141
+ # ?reduce_mode: Symbol,
142
+ # ?reduce_max_trials: Integer,
143
+ # ?reduce_timeout: Integer,
144
+ # ?reduce_max_output_bytes: Integer,
145
+ # ?reduce_max_input_bytes: Integer,
146
+ # ?reduce_format: String
45
147
  # }
46
148
 
47
149
  # Command-line pipeline coordinator.
48
150
  # rubocop:disable Metrics/ClassLength -- inline type contracts add lines without adding runtime responsibilities.
49
151
  class CLI
152
+ SUBCOMMAND_HANDLERS = {
153
+ "check" => %i[CLIAmbiguity run_check_command],
154
+ "diff" => %i[CLIAnalysis run_diff_command],
155
+ "diagnose" => %i[CLIDiagnostics run_diagnose_command],
156
+ "coverage" => %i[CLICoverage run_coverage_command], "config" => %i[CLIConfig run_config_command],
157
+ "debug" => %i[CLIDebug run_debug_command],
158
+ "doc" => %i[CLIDocumentation run_documentation_command],
159
+ "errors" => %i[CLIErrorMessages run_error_messages_command],
160
+ "equiv" => %i[CLIEquiv run_equiv_command],
161
+ "explain" => %i[CLIExplain run_explain_command],
162
+ "fmt" => %i[CLIFormatting run_format_command],
163
+ "fix" => %i[CLIFix run_fix_command],
164
+ "fuzz" => %i[CLIFuzz run_fuzz_command],
165
+ "test" => %i[CLIGrammarTests run_grammar_tests_command],
166
+ "lsp" => %i[CLILSP run_lsp_command],
167
+ "import" => %i[CLIBisonImport run_bison_import_command],
168
+ "metrics" => %i[CLIAnalysis run_metrics_command],
169
+ "migrate-check" => %i[CLIRaccMigration run_migrate_check_command],
170
+ "migrate-harness" => %i[CLIRaccMigration run_migrate_harness_command],
171
+ "reduce" => %i[CLIReduce run_reduce_command],
172
+ "samples" => %i[CLISamples run_samples_command],
173
+ "verify" => %i[CLIVerify run_verify_command],
174
+ "validate-ir" => %i[CLIIRTools run_validate_ir_command],
175
+ "compare" => %i[CLIIRTools run_compare_command]
176
+ }.freeze #: Hash[String, [Symbol, Symbol]]
177
+
50
178
  include CLICounterexampleOptions
179
+ include CLIGenerationErrorMessages
180
+ include CLIGenerationArtifacts
51
181
  include CLIOutputs
52
182
 
53
183
  # @rbs @stdout: _CLIOutput
54
184
  # @rbs @stderr: _CLIOutput
55
185
  # @rbs @options: cli_options
186
+ # @rbs @language: String
187
+ # @rbs @configuration_explicit_options: Hash[Symbol, bool]
188
+ # @rbs @generation_grammar: IR::Grammar?
189
+ # @rbs @generation_automaton: IR::Automaton?
56
190
 
57
- # @rbs (Array[String] arguments, ?stdout: _CLIOutput, ?stderr: _CLIOutput) -> Integer
58
- def self.start(arguments, stdout: $stdout, stderr: $stderr)
59
- new(stdout: stdout, stderr: stderr).run(arguments)
191
+ # rubocop:disable Layout/LineLength
192
+ # @rbs (Array[String] arguments, ?stdin: _CLIInput, ?stdout: _CLIOutput, ?stderr: _CLIOutput, ?watch_clock: (^() -> Float)?, ?watch_sleeper: (^(Float) -> void)?, ?watch_iteration_hook: (^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil))?) -> Integer
193
+ def self.start(arguments, stdin: $stdin, stdout: $stdout, stderr: $stderr, watch_clock: nil, watch_sleeper: nil,
194
+ watch_iteration_hook: nil)
195
+ new(
196
+ stdin: stdin, stdout: stdout, stderr: stderr, watch_clock: watch_clock,
197
+ watch_sleeper: watch_sleeper, watch_iteration_hook: watch_iteration_hook
198
+ ).run(arguments)
60
199
  end
61
200
 
62
- # @rbs (stdout: _CLIOutput, stderr: _CLIOutput) -> void
63
- def initialize(stdout:, stderr:)
201
+ # @rbs (?stdin: _CLIInput, stdout: _CLIOutput, stderr: _CLIOutput, ?watch_clock: (^() -> Float)?, ?watch_sleeper: (^(Float) -> void)?, ?watch_iteration_hook: (^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil))?) -> void
202
+ def initialize(stdout:, stderr:, stdin: $stdin, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil)
203
+ @stdin = stdin
64
204
  @stdout = stdout
65
205
  @stderr = stderr
66
- @options = { emit: "ruby", mode: :racc, table: :compact, line_convert: true }
206
+ @watch_clock = watch_clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
207
+ @watch_sleeper = watch_sleeper || ->(seconds) { sleep(seconds) }
208
+ @watch_iteration_hook = watch_iteration_hook || ->(_event, _iteration, _paths) {}
209
+ @language = Messages.language(ENV.fetch("IBEX_LANG", nil))
210
+ @options = {
211
+ emit: "ruby", mode: Configuration::Registry.fetch("grammar.mode").default,
212
+ table: Configuration::Registry.fetch("table.representation").default, line_convert: true
213
+ }
67
214
  .merge(CLICounterexampleOptions::DEFAULTS)
215
+ @configuration_explicit_options = {}
216
+ @generation_grammar = nil
217
+ @generation_automaton = @analysis_configuration = nil
68
218
  end
219
+ # rubocop:enable Layout/LineLength
69
220
 
70
221
  # @rbs (Array[String] arguments) -> Integer
71
222
  def run(arguments)
223
+ arguments = extract_language(arguments)
224
+ @generation_grammar = nil
225
+ @generation_automaton = @analysis_configuration = nil
226
+ subcommand = dispatch_subcommand(arguments)
227
+ return subcommand unless subcommand.nil?
228
+
72
229
  parser = option_parser
73
230
  remaining = parser.parse(arguments)
231
+ validate_watch_information_options
74
232
  information = informational_result(parser)
75
233
  return information unless information.nil?
76
234
 
77
- process_grammar(input_path(remaining))
78
- rescue OptionParser::ParseError, Ibex::Error, Errno::ENOENT => e
79
- @stderr.puts(e.message)
235
+ validate_messages_options
236
+ validate_generation_options
237
+ path = input_path(remaining)
238
+ validate_generation_paths!(path)
239
+ if @options[:watch]
240
+ run_watch_feature(path)
241
+ else
242
+ process_grammar(path)
243
+ end
244
+ rescue OptionParser::ParseError, Ibex::Error, SystemCallError, SystemStackError => e
245
+ @stderr.puts(Messages.translate("cli.error", language: @language, detail: e.message))
80
246
  1
81
247
  end
82
248
 
83
249
  private
84
250
 
251
+ # @rbs (Symbol name) -> void
252
+ def mark_configuration_option(name)
253
+ @configuration_explicit_options[name] = true
254
+ end
255
+
256
+ # OptionParser accepts a heterogeneous set of operation and configuration
257
+ # values. The CLIAdapter is the validation boundary for this dynamic map.
258
+ # @rbs (Symbol name, untyped value) -> void
259
+ def set_configuration_option(name, value)
260
+ @options[name] = value
261
+ mark_configuration_option(name)
262
+ end
263
+
264
+ # @rbs (String value) -> void
265
+ def select_configuration_mode(value)
266
+ set_configuration_option(:mode, value.to_sym)
267
+ end
268
+
269
+ # @rbs () -> Configuration::Resolver
270
+ def effective_configuration
271
+ grammar = @generation_grammar
272
+ contract = grammar&.parser_contract
273
+ @analysis_configuration || Configuration::CLIAdapter.new(
274
+ @options, explicit_keys: @configuration_explicit_options.keys
275
+ ).resolve(
276
+ grammar: contract&.configuration_values || {},
277
+ locations: contract&.configuration_locations || {}
278
+ )
279
+ end
280
+
281
+ # Resolve a command-local legacy option hash without promoting it into the
282
+ # reusable CLI instance's generation settings.
283
+ # @rbs (Hash[Symbol, untyped] options, explicit_keys: Array[Symbol]) -> Configuration::Resolver
284
+ def resolve_configuration_options(options, explicit_keys:)
285
+ Configuration::CLIAdapter.new(options, explicit_keys: explicit_keys).resolve
286
+ end
287
+
288
+ # @rbs (Hash[Symbol, untyped] options, String name) -> untyped
289
+ def local_configuration_value(options, name)
290
+ explicit_keys = options.fetch(:configuration_explicit)
291
+ resolve_configuration_options(options, explicit_keys: explicit_keys).value(name)
292
+ end
293
+
294
+ # @rbs (Hash[Symbol, untyped] options, Symbol name, untyped value) -> void
295
+ def set_local_configuration_option(options, name, value)
296
+ options[name] = value
297
+ options.fetch(:configuration_explicit) << name
298
+ end
299
+
300
+ # Configuration values have already been validated by Configuration::Resolver;
301
+ # callers narrow the value according to the option they are consuming.
302
+ # @rbs (String name) -> untyped
303
+ def configuration_value(name)
304
+ effective_configuration.value(name)
305
+ end
306
+
307
+ # @rbs (Array[String] arguments) -> Array[String]
308
+ def extract_language(arguments)
309
+ remaining = [] #: Array[String]
310
+ index = 0
311
+ while index < arguments.length
312
+ argument = arguments.fetch(index)
313
+ if argument == "--lang"
314
+ value = arguments[index + 1]
315
+ raise OptionParser::MissingArgument, "--lang" unless value
316
+
317
+ @language = Messages.language(value)
318
+ index += 2
319
+ elsif argument.start_with?("--lang=")
320
+ value = argument.delete_prefix("--lang=")
321
+ raise OptionParser::MissingArgument, "--lang" if value.empty?
322
+
323
+ @language = Messages.language(value)
324
+ index += 1
325
+ else
326
+ remaining << argument
327
+ index += 1
328
+ end
329
+ end
330
+ remaining
331
+ end
332
+
333
+ # @rbs (Array[String] arguments) -> Integer?
334
+ def dispatch_subcommand(arguments)
335
+ definition = SUBCOMMAND_HANDLERS[arguments.first]
336
+ return unless definition
337
+
338
+ feature, handler = definition
339
+ activate_cli_feature(feature)
340
+ send(handler, arguments.drop(1))
341
+ end
342
+
343
+ # @rbs (Symbol feature) -> void
344
+ def activate_cli_feature(feature)
345
+ extension = Ibex.const_get(feature)
346
+ extend extension unless singleton_class.ancestors.include?(extension)
347
+ end
348
+
349
+ # @rbs (String path) -> Integer
350
+ def run_watch_feature(path)
351
+ activate_cli_feature(:CLIWatch)
352
+ send(:run_watch, path)
353
+ end
354
+
85
355
  # @rbs () -> OptionParser
86
356
  def option_parser
87
357
  OptionParser.new do |options|
88
358
  options.banner = "Usage: ibex [options] grammarfile"
89
359
  add_pipeline_options(options)
90
360
  add_output_options(options)
361
+ add_error_messages_generation_option(options)
91
362
  add_compatibility_options(options)
92
363
  add_information_options(options)
364
+ add_subcommand_help(options)
93
365
  end
94
366
  end
95
367
 
96
368
  # @rbs (OptionParser options) -> void
369
+ def add_subcommand_help(options)
370
+ options.separator("")
371
+ options.separator("Subcommands:")
372
+ options.separator(" check --ambiguity search for ambiguity within explicit budgets")
373
+ options.separator(CONFIG_SUBCOMMAND_HELP)
374
+ options.separator(" debug AUTOMATON [TOKEN] simulate validated Automaton IR tables")
375
+ options.separator(" diagnose collect frontend diagnostics")
376
+ options.separator(" diff OLD NEW classify grammar and automaton changes")
377
+ options.separator(" doc render grammar documentation")
378
+ options.separator(" errors --list|--update list or update example-keyed syntax error messages")
379
+ options.separator(" equiv LEFT RIGHT search for bounded language differences")
380
+ options.separator(" explain explain selected parser conflicts")
381
+ options.separator(" fmt format grammar source")
382
+ options.separator(" fix propose bounded-equivalent conflict repairs")
383
+ options.separator(" fuzz run bounded grammar-derived differential fuzzing")
384
+ options.separator(" test run grammar-declared source examples")
385
+ options.separator(" lsp run the language server over stdio")
386
+ options.separator(" import bison FILE import Bison grammar structure for analysis")
387
+ options.separator(" migrate-check statically check a racc grammar migration")
388
+ options.separator(" migrate-harness generate a differential subprocess harness")
389
+ options.separator(" metrics GRAMMAR report deterministic grammar/table metrics")
390
+ options.separator(" reduce delta-debug a failing token, line, or byte sequence")
391
+ options.separator(" samples generate bounded terminal sentences")
392
+ options.separator(" verify AUTOMATON independently verify Automaton IR semantics")
393
+ options.separator(" validate-ir FILE validate the current IR document")
394
+ options.separator(" compare BEFORE AFTER compare current IR documents")
395
+ end
396
+
397
+ # @rbs (OptionParser options) -> void
398
+ # rubocop:disable Metrics/MethodLength -- pipeline flags share one parser boundary.
97
399
  def add_pipeline_options(options)
98
- options.on("--emit=FORMAT", "ast, grammar-ir, automaton-ir, or ruby") { |value| @options[:emit] = value }
400
+ options.on("--emit=FORMAT", "ast, sets, lexer-ir, grammar-ir, automaton-ir, or ruby") do |value|
401
+ @options[:emit] = value
402
+ end
99
403
  options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
100
404
  @options[:from] = value
101
405
  end
102
- options.on("--mode=MODE", %w[racc extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
406
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| select_configuration_mode(value) }
103
407
  options.on("--table=FORMAT", %w[plain compact], "parser table format") do |value|
104
- @options[:table] = value.to_sym
408
+ set_configuration_option(:table, value.to_sym)
409
+ end
410
+ options.on(
411
+ "--cst-trivia=POLICY",
412
+ %w[leading balanced drop attach],
413
+ "leading, balanced, drop, or attach (alias for leading)"
414
+ ) do |value|
415
+ set_configuration_option(:cst_trivia, value.to_sym)
416
+ end
417
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
418
+ set_configuration_option(:algorithm, value.to_sym)
419
+ end
420
+ options.on("--ielr-strategy=NAME", %w[direct partition], "IELR construction strategy") do |value|
421
+ @options[:ielr_strategy] = value.to_sym
422
+ end
423
+ options.on("--ielr-report", "report canonical-vs-IELR action differences") do
424
+ @options[:ielr_report] = true
425
+ end
426
+ options.on("--remove-unreachable", "compact unreachable IELR states after resolution") do
427
+ @options[:remove_unreachable] = true
428
+ end
429
+ options.on("--suggest-ielr", "check whether IELR removes unexpected LALR conflicts") do
430
+ @options[:suggest_ielr] = true
105
431
  end
106
- options.on("--algorithm=NAME", %w[slr lalr lr1], "parser construction algorithm") do |value|
107
- @options[:algorithm] = value.to_sym
432
+ options.on("--entry-isolation", "build independent state sets for each start symbol") do
433
+ set_configuration_option(:entry_isolation, true)
108
434
  end
109
435
  options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
110
436
  @options[:warnings] = warning_categories(value)
111
437
  end
438
+ options.on("--watch", "regenerate file outputs when grammar sources change") { @options[:watch] = true }
112
439
  end
440
+ # rubocop:enable Metrics/MethodLength
113
441
 
114
442
  # @rbs (OptionParser options) -> void
115
443
  def add_output_options(options)
116
444
  options.on("-o", "--output-file=FILE", "generated parser path") { |value| @options[:output] = value }
117
- options.on("-E", "--embedded", "embed the Pure Ruby runtime") { @options[:embedded] = true }
118
- options.on("-t", "--debug", "generate a debug-capable parser") { @options[:debug] = true }
119
- options.on("-g", "obsolete alias for --debug") { @options[:debug] = true }
445
+ options.on("-E", "--embedded", "embed the Pure Ruby runtime") do
446
+ set_configuration_option(:embedded, true)
447
+ end
448
+ options.on("-t", "--debug", "generate a debug-capable parser") do
449
+ set_configuration_option(:debug, true)
450
+ end
451
+ options.on("-g", "obsolete alias for --debug") do
452
+ set_configuration_option(:debug, true)
453
+ end
120
454
  options.on("-v", "--verbose", "write an automaton report") { @options[:verbose] = true }
121
455
  add_counterexample_options(options)
122
- options.on("--rbs[=FILE]", "write an RBS signature (defaults beside parser)") do |value|
123
- @options[:rbs] = value || true
456
+ add_signature_output_options(options)
457
+ options.on("--manifest[=FILE]", "write a generation manifest (defaults beside parser)") do |value|
458
+ @options[:manifest] = value || true
124
459
  end
125
460
  options.on("--dot=FILE", "write Graphviz DOT") { |value| @options[:dot] = value }
461
+ options.on("--mermaid=FILE", "write a Mermaid flowchart") { |value| @options[:mermaid] = value }
126
462
  options.on("--html=FILE", "write a self-contained HTML report") { |value| @options[:html] = value }
463
+ options.on("--railroad=FILE", "write a self-contained SVG railroad diagram") do |value|
464
+ @options[:railroad] = value
465
+ end
127
466
  options.on("-O", "--log-file=FILE", "automaton report path") do |value|
128
467
  @options[:verbose] = true
129
468
  @options[:log_file] = value
130
469
  end
131
470
  options.on("-e", "--executable [RUBY]", "add a shebang") do |value|
132
- @options[:executable] = value || "/usr/bin/env ruby"
471
+ set_configuration_option(:executable, value || "/usr/bin/env ruby")
472
+ end
473
+ end
474
+
475
+ # @rbs (OptionParser options) -> void
476
+ def add_signature_output_options(options)
477
+ options.on("--rbs[=FILE]", "write an RBS signature (defaults beside parser)") do |value|
478
+ @options[:rbs] = value || true
479
+ end
480
+ options.on("--action-source[=FILE]", "write static-check-only action Ruby (defaults beside parser)") do |value|
481
+ @options[:action_source] = value || true
133
482
  end
134
483
  end
135
484
 
136
485
  # @rbs (OptionParser options) -> void
137
486
  def add_compatibility_options(options)
138
- options.on("-F", "--frozen", "emit frozen string literals") { @options[:frozen] = true }
487
+ options.on("-F", "--frozen", "emit frozen string literals") do
488
+ set_configuration_option(:frozen, true)
489
+ end
139
490
  options.on("--line-convert-all", "convert all source lines") do
140
- @options[:line_convert] = true
141
491
  @options[:line_convert_all] = true
492
+ set_configuration_option(:line_convert, true)
142
493
  end
143
494
  options.on("-l", "--no-line-convert", "use generated-file action lines") do
144
- @options[:line_convert] = false
145
495
  @options[:line_convert_all] = false
496
+ set_configuration_option(:line_convert, false)
146
497
  end
147
- options.on("-a", "--no-omit-actions", "generate implicit action methods") { @options[:omit_actions] = false }
148
- options.on("--superclass=CLASS", "override parser superclass") { |value| @options[:superclass] = value }
498
+ options.on("-a", "--no-omit-actions", "generate implicit action methods") do
499
+ set_configuration_option(:omit_actions, false)
500
+ end
501
+ options.on("--superclass=CLASS", "override parser superclass") do |value|
502
+ set_configuration_option(:superclass, value)
503
+ end
504
+ options.on("--check", "verify generated parser content without rewriting") { @options[:verify_output] = true }
149
505
  options.on("-C", "--check-only", "check grammar and exit") { @options[:check_only] = true }
150
506
  options.on("-S", "--output-status", "show pipeline status") { @options[:status] = true }
151
507
  options.on("-P", "accept the compatibility profiling flag") { @options[:profile] = true }
@@ -154,12 +510,55 @@ module Ibex
154
510
 
155
511
  # @rbs (OptionParser options) -> void
156
512
  def add_information_options(options)
513
+ options.on("--lang=LANG", "built-in diagnostic language (en or ja)") do |value|
514
+ @language = Messages.language(value)
515
+ end
157
516
  options.on("--version", "show version") { @options[:version] = true }
158
517
  options.on("--runtime-version", "show runtime version") { @options[:runtime_version] = true }
159
518
  options.on("--copyright", "show copyright") { @options[:copyright] = true }
160
519
  options.on("--help", "show help") { @options[:help] = true }
161
520
  end
162
521
 
522
+ # @rbs @analysis_configuration: Configuration::Resolver?
523
+
524
+ # @rbs (IR::Grammar grammar, ?options: Hash[Symbol, untyped], ?explicit_keys: Array[Symbol]) -> IR::Grammar
525
+ def activate_analysis_grammar(grammar, options: @options, explicit_keys: @configuration_explicit_options.keys)
526
+ adapter = Configuration::CLIAdapter.new(options, explicit_keys: explicit_keys)
527
+ cli = adapter.configuration_values
528
+ contract = grammar.parser_contract
529
+ grammar_values = contract&.configuration_values || {}
530
+ locations = contract&.configuration_locations || {}
531
+ overrides = {} #: Hash[String, Configuration::config_value]
532
+ algorithm = "parser.algorithm"
533
+ if grammar_values.key?(algorithm) && cli.key?(algorithm) &&
534
+ grammar_values.fetch(algorithm) != cli.fetch(algorithm)
535
+ overrides[algorithm] = cli.delete(algorithm)
536
+ end
537
+ source_locations = {} #: Hash[Symbol, Hash[String, Location]]
538
+ source_locations[:grammar] = locations unless locations.empty?
539
+ configuration = Configuration::Resolver.new(
540
+ grammar: grammar_values, cli: cli, analysis_overrides: overrides, locations: source_locations
541
+ )
542
+ @analysis_configuration = configuration
543
+ active = noncanonical_analysis_grammar(grammar, configuration)
544
+ @generation_grammar = active
545
+ report_noncanonical_analysis(configuration)
546
+ active
547
+ end
548
+
549
+ # @rbs (IR::Grammar grammar, Hash[Symbol, untyped] options, Array[Symbol] explicit_keys) ->
550
+ # [IR::Grammar, Symbol, IR::Automaton]
551
+ def construct_analysis_automaton(grammar, options, explicit_keys)
552
+ active = activate_analysis_grammar(grammar, options: options, explicit_keys: explicit_keys)
553
+ algorithm = configuration_value("parser.algorithm") #: Symbol
554
+ automaton = LALR::Builder.new(
555
+ active, algorithm: algorithm, ielr_strategy: options.fetch(:ielr_strategy, :partition),
556
+ remove_unreachable: options.fetch(:remove_unreachable, false),
557
+ entry_isolation: configuration_value("parser.entries") == :isolated
558
+ ).build
559
+ [active, algorithm, automaton]
560
+ end
561
+
163
562
  # @rbs (OptionParser parser) -> Integer?
164
563
  def informational_result(parser)
165
564
  return print_version if @options[:version] || @options[:runtime_version]
@@ -177,34 +576,230 @@ module Ibex
177
576
  path
178
577
  end
179
578
 
579
+ # @rbs () -> void
580
+ def validate_generation_options
581
+ validate_automaton_construction_options
582
+
583
+ validate_watch_generation_options if @options[:watch]
584
+ validate_manifest_generation_options
585
+ validate_action_source_generation_options
586
+ validate_verification_generation_options
587
+ end
588
+
589
+ # @rbs () -> void
590
+ def validate_automaton_construction_options
591
+ return unless @options[:from] == "automaton-ir"
592
+
593
+ if @configuration_explicit_options[:algorithm]
594
+ raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with --from=automaton-ir"
595
+ end
596
+ return unless @configuration_explicit_options[:entry_isolation]
597
+
598
+ raise Ibex::Error, "(cli):1:1: --entry-isolation cannot be combined with --from=automaton-ir"
599
+ end
600
+
601
+ # @rbs () -> void
602
+ def validate_manifest_generation_options
603
+ return unless @options[:manifest]
604
+
605
+ raise Ibex::Error, "(cli):1:1: --manifest requires --emit=ruby" unless @options[:emit] == "ruby"
606
+ raise Ibex::Error, "(cli):1:1: --manifest and --check-only cannot be combined" if @options[:check_only]
607
+ end
608
+
609
+ # @rbs () -> void
610
+ def validate_action_source_generation_options
611
+ return unless @options[:action_source]
612
+
613
+ raise Ibex::Error, "(cli):1:1: --action-source requires --emit=ruby" unless @options[:emit] == "ruby"
614
+ raise Ibex::Error, "(cli):1:1: --action-source and --check-only cannot be combined" if @options[:check_only]
615
+ end
616
+
617
+ # @rbs () -> void
618
+ def validate_verification_generation_options
619
+ return unless @options[:verify_output]
620
+
621
+ raise Ibex::Error, "(cli):1:1: --check requires --emit=ruby" unless @options[:emit] == "ruby"
622
+ raise Ibex::Error, "(cli):1:1: --check and --check-only cannot be combined" if @options[:check_only]
623
+ end
624
+
625
+ # @rbs () -> void
626
+ def validate_watch_information_options
627
+ return unless @options[:watch]
628
+ return unless %i[version runtime_version copyright help].any? { |key| @options[key] }
629
+
630
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with information options"
631
+ end
632
+
633
+ # @rbs () -> void
634
+ def validate_watch_generation_options
635
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --from" if @options[:from]
636
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check" if @options[:verify_output]
637
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check-only" if @options[:check_only]
638
+ return if @options[:emit] == "ruby"
639
+
640
+ raise Ibex::Error, "(cli):1:1: --watch requires --emit=ruby with file outputs"
641
+ end
642
+
643
+ # @rbs (String input_path, ?source_paths: Array[String]) -> void
644
+ def validate_generation_paths!(input_path, source_paths: [input_path])
645
+ outputs = generation_paths(input_path).except(:input, :messages)
646
+ paths = outputs.filter_map do |kind, path|
647
+ [kind, path] if path
648
+ end #: Array[[Symbol, String]]
649
+ source_entries = source_paths.map.with_index do |path, index|
650
+ [index.zero? ? :input : :"include_#{index}", path]
651
+ end
652
+ source_entries << [:messages, @options[:messages]] if @options[:messages]
653
+ paths.concat(source_entries)
654
+ collision = paths.combination(2).find do |pair|
655
+ left = pair.fetch(0)
656
+ right = pair.fetch(1)
657
+ same_file_target?(left.fetch(1), right.fetch(1))
658
+ end
659
+ return unless collision
660
+
661
+ labels = collision.map { |kind, path| "#{kind}=#{path}" }
662
+ raise Ibex::Error, "(cli):1:1: paths must be distinct: #{labels.join(', ')}"
663
+ end
664
+
665
+ # @rbs (String left, String right) -> bool
666
+ def same_file_target?(left, right)
667
+ expanded_left = File.expand_path(left)
668
+ expanded_right = File.expand_path(right)
669
+ return true if expanded_left == expanded_right
670
+
671
+ return true if File.exist?(expanded_left) && File.exist?(expanded_right) &&
672
+ File.identical?(expanded_left, expanded_right)
673
+
674
+ portable_target_key(expanded_left) == portable_target_key(expanded_right)
675
+ rescue SystemCallError
676
+ expanded_left == expanded_right
677
+ end
678
+
679
+ # @rbs (String path) -> [String, String]
680
+ def portable_target_key(path)
681
+ canonical = canonical_target_path(path)
682
+ basename = File.basename(canonical)
683
+ folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
684
+ basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
685
+ else
686
+ basename.downcase
687
+ end
688
+ [File.dirname(canonical), folded]
689
+ end
690
+
691
+ # @rbs (String path, ?Hash[String, bool] seen) -> String
692
+ def canonical_target_path(path, seen = {})
693
+ suffix = [] #: Array[String]
694
+ cursor = File.expand_path(path)
695
+ until File.exist?(cursor) || File.symlink?(cursor)
696
+ parent = File.dirname(cursor)
697
+ return path if parent == cursor
698
+
699
+ suffix.unshift(File.basename(cursor))
700
+ cursor = parent
701
+ end
702
+ if File.symlink?(cursor)
703
+ raise Errno::ELOOP, cursor if seen[cursor]
704
+
705
+ seen[cursor] = true
706
+ target = File.expand_path(File.readlink(cursor), File.dirname(cursor))
707
+ return File.join(canonical_target_path(target, seen), *suffix)
708
+ end
709
+
710
+ File.join(File.realpath(cursor), *suffix)
711
+ end
712
+
713
+ # @rbs (String input_path) -> Hash[Symbol, String?]
714
+ def generation_paths(input_path)
715
+ paths = { input: input_path, messages: @options[:messages] } #: Hash[Symbol, String?]
716
+ if @options[:emit] == "ruby" && !@options[:check_only]
717
+ output = @options[:output] || default_output_path(input_path, ".rb")
718
+ paths[:parser] = output
719
+ paths[:rbs] = rbs_output_path(output) if @options[:rbs]
720
+ paths[:action_source] = action_source_output_path(output) if @options[:action_source]
721
+ paths[:manifest] = manifest_output_path_for(output) if @options[:manifest]
722
+ end
723
+ unless @options[:verify_output]
724
+ paths.merge!(dot: @options[:dot], mermaid: @options[:mermaid], html: @options[:html],
725
+ railroad: @options[:railroad])
726
+ paths[:report] = @options[:log_file] || default_output_path(input_path, ".output") if @options[:verbose]
727
+ end
728
+ paths
729
+ end
730
+
180
731
  # @rbs (String path) -> Integer
181
732
  def process_grammar(path)
182
733
  return process_ir(path) if @options[:from]
183
734
 
735
+ begin_artifact_generation
184
736
  report_status("reading #{path}")
185
- ast = Frontend::Parser.new(File.read(path), file: path, mode: @options[:mode]).parse
186
- return emit_ast(ast) if @options[:emit] == "ast"
737
+ resolution = resolve_grammar_path(path)
738
+ @generation_inputs = @last_resolver.source_records
739
+ @generation_sources = @generation_inputs.map(&:path)
740
+ validate_generation_paths!(path, source_paths: resolution.files)
741
+ return emit_ast(resolution.root) if @options[:emit] == "ast"
187
742
 
188
- grammar = Normalizer.new(ast, mode: @options[:mode]).normalize
743
+ grammar = Normalizer.new(resolution, mode: configuration_value("grammar.mode")).normalize
189
744
  dispatch_grammar(grammar, path)
190
745
  end
191
746
 
747
+ # @rbs (String path) -> Frontend::Resolution
748
+ def resolve_grammar_path(path)
749
+ loader = Frontend::SourceLoader.new(record_reads: true)
750
+ @last_resolver = Frontend::Resolver.new(path, mode: configuration_value("grammar.mode"), loader: loader)
751
+ @last_resolver.resolve
752
+ end
753
+
754
+ # @rbs (String path) -> IR::Grammar
755
+ def normalize_grammar_path(path)
756
+ source = File.binread(path)
757
+ if bison_source?(source)
758
+ require_relative "bison_import"
759
+ imported = BisonImport::Importer.new(source, file: path).run
760
+ ast = Frontend::Parser.new(imported.source, file: path, mode: :extended).parse
761
+ return Normalizer.new(ast, mode: :extended).normalize
762
+ end
763
+
764
+ Normalizer.new(resolve_grammar_path(path), mode: configuration_value("grammar.mode")).normalize
765
+ end
766
+
767
+ # @rbs (String source) -> bool
768
+ def bison_source?(source)
769
+ source.lines.count { |line| line.match?(%r{^\s*%%(?:\s|/|$)}) } >= 2
770
+ end
771
+
192
772
  # @rbs (String path) -> Integer
193
773
  def process_ir(path)
774
+ require_relative "ir/validator"
775
+
776
+ begin_artifact_generation
194
777
  report_status("reading #{path}")
195
- value = IR::Serialize.load(File.read(path))
778
+ source = File.binread(path)
779
+ input = record_generation_input(path, source)
780
+ validate_generation_paths!(path, source_paths: [input.path])
781
+ value = IR::Validator.validate(source)
196
782
  expected = @options[:from] == "grammar-ir" ? IR::Grammar : IR::Automaton
197
783
  raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input" unless value.is_a?(expected)
198
784
 
199
785
  return dispatch_grammar(value, path) if value.is_a?(IR::Grammar)
786
+ return dispatch_automaton(value, path) if value.is_a?(IR::Automaton)
200
787
 
201
- dispatch_automaton(value, path)
788
+ raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input"
202
789
  end
203
790
 
204
791
  # @rbs (IR::Grammar grammar, String path) -> Integer
205
792
  def dispatch_grammar(grammar, path)
793
+ activate_generation_grammar(grammar)
206
794
  handle_grammar_warnings(grammar, path)
207
- return 0 if @options[:check_only]
795
+ if @options[:check_only]
796
+ build_automaton(grammar, path) if @options[:warnings]&.include?(:error)
797
+ return 0
798
+ end
799
+
800
+ write_railroad(grammar) unless @options[:verify_output]
801
+ return emit_sets(grammar) if @options[:emit] == "sets"
802
+ return emit_lexer(grammar) if @options[:emit] == "lexer-ir"
208
803
  return emit_grammar(grammar) if @options[:emit] == "grammar-ir"
209
804
  return emit_automaton(grammar, path) if @options[:emit] == "automaton-ir"
210
805
  return emit_ruby(grammar, path) if @options[:emit] == "ruby"
@@ -214,8 +809,13 @@ module Ibex
214
809
 
215
810
  # @rbs (IR::Automaton automaton, String path) -> Integer
216
811
  def dispatch_automaton(automaton, path)
812
+ activate_generation_grammar(automaton.grammar)
217
813
  handle_grammar_warnings(automaton.grammar, path)
218
814
  return 0 if @options[:check_only]
815
+
816
+ write_railroad(automaton.grammar) unless @options[:verify_output]
817
+ return emit_sets(automaton.grammar) if @options[:emit] == "sets"
818
+ return emit_lexer(automaton.grammar) if @options[:emit] == "lexer-ir"
219
819
  return emit_grammar(automaton.grammar) if @options[:emit] == "grammar-ir"
220
820
  return emit_loaded_automaton(automaton, path) if @options[:emit] == "automaton-ir"
221
821
 
@@ -227,6 +827,36 @@ module Ibex
227
827
  raise Ibex::Error, "(cli):1:1: AST cannot be reconstructed from Automaton IR"
228
828
  end
229
829
 
830
+ # @rbs (IR::Grammar grammar) -> void
831
+ def activate_generation_grammar(grammar)
832
+ @analysis_configuration = nil
833
+ @generation_grammar = grammar
834
+ effective_configuration
835
+ end
836
+
837
+ # @rbs (IR::Grammar grammar, Configuration::Resolver configuration) -> IR::Grammar
838
+ def noncanonical_analysis_grammar(grammar, configuration)
839
+ selection = configuration.fetch("parser.algorithm")
840
+ return grammar if selection.canonical
841
+
842
+ require_relative "configuration/analysis_grammar"
843
+ algorithm = selection.value #: Symbol
844
+ Configuration::AnalysisGrammar.for_algorithm(grammar, algorithm)
845
+ end
846
+
847
+ # @rbs (Configuration::Resolver configuration) -> void
848
+ def report_noncanonical_analysis(configuration)
849
+ selection = configuration.fetch("parser.algorithm")
850
+ return if selection.canonical
851
+
852
+ analysis = selection.to_h.fetch("analysis") #: Hash[String, Configuration::json_value]
853
+ @stderr.puts(
854
+ "noncanonical analysis configuration: parser.algorithm " \
855
+ "declared=#{analysis.fetch('declared')} selected=#{analysis.fetch('selected')} " \
856
+ "override=true canonical_generation=false"
857
+ )
858
+ end
859
+
230
860
  # @rbs () -> Integer
231
861
  def print_version
232
862
  @stdout.puts("ibex #{VERSION}")
@@ -253,13 +883,40 @@ module Ibex
253
883
 
254
884
  # @rbs (IR::Grammar grammar) -> Integer
255
885
  def emit_grammar(grammar)
886
+ finish_artifact_generation(@generation_sources)
256
887
  @stdout.write(IR::Serialize.dump(grammar))
257
888
  0
258
889
  end
259
890
 
891
+ # @rbs (IR::Grammar grammar) -> Integer
892
+ def emit_lexer(grammar)
893
+ lexer = grammar.lexer
894
+ raise Ibex::Error, "(cli):1:1: grammar does not declare a lexer" unless lexer
895
+
896
+ finish_artifact_generation(@generation_sources)
897
+ @stdout.write(IR::Serialize.dump(lexer))
898
+ 0
899
+ end
900
+
901
+ # @rbs (IR::Grammar grammar) -> Integer
902
+ def emit_sets(grammar)
903
+ sets = Analysis::Sets.new(grammar)
904
+ nonterminals = grammar.nonterminals.sort_by(&:name)
905
+ output = {
906
+ nullable: nonterminals.filter_map { |symbol| symbol.name if sets.nullable?(symbol) },
907
+ first: nonterminals.to_h { |symbol| [symbol.name, sets.first(symbol).sort] },
908
+ follow: nonterminals.to_h { |symbol| [symbol.name, sets.follow(symbol).sort] }
909
+ }
910
+ finish_artifact_generation(@generation_sources)
911
+ @stdout.puts(JSON.pretty_generate(output))
912
+ 0
913
+ end
914
+
260
915
  # @rbs (IR::Grammar grammar, String input_path) -> Integer
261
916
  def emit_automaton(grammar, input_path)
262
- @stdout.write(IR::Serialize.dump(build_automaton(grammar, input_path)))
917
+ automaton = build_automaton(grammar, input_path)
918
+ finish_artifact_generation(@generation_sources)
919
+ @stdout.write(IR::Serialize.dump(automaton))
263
920
  0
264
921
  end
265
922
 
@@ -271,63 +928,176 @@ module Ibex
271
928
 
272
929
  # @rbs (IR::Automaton automaton, String input_path) -> Integer
273
930
  def generate_ruby(automaton, input_path)
931
+ @generation_automaton = automaton
932
+ line_mapping = configuration_value("source.line_mapping") #: Symbol
933
+ executable = configuration_value("build.executable") #: String?
934
+ table = configuration_value("table.representation") #: Symbol | String
935
+ embedded = configuration_value("runtime.embedded") #: bool
936
+ debug = configuration_value("build.debug") #: bool
937
+ omit_action_call = configuration_value("actions.omit_calls") #: bool?
938
+ superclass = configuration_value("parser.superclass") #: String?
939
+ cst_trivia = configuration_value("cst.trivia") #: Symbol | String
274
940
  source = Codegen::Ruby.new(
275
- automaton, table: @options[:table], embedded: @options.fetch(:embedded, false),
276
- line_convert: @options.fetch(:line_convert), debug: @options.fetch(:debug, false),
277
- line_convert_all: @options.fetch(:line_convert_all, false),
278
- omit_action_call: @options[:omit_actions], superclass: @options[:superclass],
279
- executable: @options[:executable]
941
+ automaton, table: table, embedded: embedded,
942
+ line_convert: line_mapping != :none, debug: debug,
943
+ line_convert_all: line_mapping == :all,
944
+ omit_action_call: omit_action_call, superclass: superclass,
945
+ executable: executable, cst_trivia: cst_trivia,
946
+ error_messages: configured_error_messages(automaton)
280
947
  ).generate
281
948
  output_path = @options[:output] || default_output_path(input_path, ".rb")
282
- File.write(output_path, source)
283
- File.chmod(0o755, output_path) if @options[:executable]
284
- report_status("wrote #{output_path}")
949
+ action_source = action_source_source(automaton) if @options[:action_source]
950
+ write_action_source(output_path, action_source) if action_source
951
+ register_artifact(:parser, output_path, source, mode: (0o755 if executable), status: true)
285
952
  write_rbs(automaton, output_path) if @options[:rbs]
953
+ finish_artifact_generation(@generation_sources)
954
+ end
955
+
956
+ # @rbs (IR::Automaton automaton, String output_path, String source, String? action_source) -> Integer
957
+ def verify_generated_outputs(automaton, output_path, source, action_source)
958
+ verify_file(output_path, source, "parser")
959
+ verify_file(rbs_output_path(output_path), rbs_source(automaton), "RBS signature") if @options[:rbs]
960
+ verify_file(action_source_output_path(output_path), action_source, "action source") if action_source
961
+ report_status("verified #{output_path}")
286
962
  0
287
963
  end
288
964
 
965
+ # @rbs (String path, String source, String label) -> void
966
+ def verify_file(path, source, label)
967
+ raise Ibex::Error, "#{path}:1:1: generated #{label} is missing" unless File.exist?(path)
968
+ return if File.binread(path) == source
969
+
970
+ raise Ibex::Error, "#{path}:1:1: generated #{label} is stale; regenerate it with the same options"
971
+ end
972
+
289
973
  # @rbs (IR::Automaton automaton, String output_path) -> void
290
974
  def write_rbs(automaton, output_path)
975
+ path = rbs_output_path(output_path)
976
+ source = rbs_source(automaton)
977
+ register_artifact(:rbs, path, source, status: true)
978
+ end
979
+
980
+ # @rbs (String output_path) -> String
981
+ def rbs_output_path(output_path)
291
982
  configured_path = @options[:rbs]
292
983
  path = configured_path == true ? default_output_path(output_path, ".rbs") : configured_path
293
984
  raise ArgumentError, "RBS output path is required" unless path.is_a?(String)
294
985
 
295
- source = Codegen::RBS.new(automaton, superclass: @options[:superclass]).generate
296
- File.write(path, source)
297
- report_status("wrote #{path}")
986
+ path
987
+ end
988
+
989
+ # @rbs (IR::Automaton automaton) -> String
990
+ def rbs_source(automaton)
991
+ require_relative "codegen/rbs"
992
+ superclass = configuration_value("parser.superclass") #: String?
993
+ omit_action_call = configuration_value("actions.omit_calls") #: bool?
994
+ Codegen::RBS.new(
995
+ automaton, superclass: superclass, omit_action_call: omit_action_call
996
+ ).generate
997
+ end
998
+
999
+ # @rbs (String output_path, String source) -> void
1000
+ def write_action_source(output_path, source)
1001
+ path = action_source_output_path(output_path)
1002
+ register_artifact(:action_source, path, source, status: true)
1003
+ end
1004
+
1005
+ # @rbs (String output_path) -> String
1006
+ def action_source_output_path(output_path)
1007
+ configured_path = @options[:action_source]
1008
+ path = configured_path == true ? default_output_path(output_path, ".actions.rb") : configured_path
1009
+ raise Ibex::Error, "(cli):1:1: action source output path is required" unless path.is_a?(String) && !path.empty?
1010
+
1011
+ path
1012
+ end
1013
+
1014
+ # @rbs (IR::Automaton automaton) -> String
1015
+ def action_source_source(automaton)
1016
+ require_relative "codegen/action_source"
1017
+ Codegen::ActionSource.new(
1018
+ automaton, omit_action_call: configuration_value("actions.omit_calls")
1019
+ ).generate
298
1020
  end
299
1021
 
300
1022
  # @rbs (IR::Automaton automaton, String input_path) -> Integer
301
1023
  def emit_loaded_automaton(automaton, input_path)
302
1024
  prepare_loaded_automaton(automaton, input_path)
1025
+ finish_artifact_generation(@generation_sources)
303
1026
  @stdout.write(IR::Serialize.dump(automaton))
304
1027
  0
305
1028
  end
306
1029
 
307
1030
  # @rbs (IR::Grammar grammar, String input_path) -> IR::Automaton
308
1031
  def build_automaton(grammar, input_path)
309
- report_status("building LALR automaton")
310
- automaton = LALR::Builder.new(grammar, algorithm: @options[:algorithm] || :lalr).build
1032
+ algorithm = configuration_value("parser.algorithm")
1033
+ report_status("building #{algorithm} automaton")
1034
+ automaton = LALR::Builder.new(
1035
+ grammar, algorithm: algorithm, ielr_strategy: @options.fetch(:ielr_strategy, :partition),
1036
+ remove_unreachable: @options.fetch(:remove_unreachable, false),
1037
+ entry_isolation: configuration_value("parser.entries") == :isolated
1038
+ ).build
1039
+ write_ielr_report(grammar, automaton) if @options[:ielr_report]
311
1040
  report_conflicts(automaton, input_path)
312
- write_report(automaton, input_path) if @options[:verbose]
313
- write_visualizations(automaton)
1041
+ suggest_ielr(automaton, input_path)
1042
+ write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
1043
+ write_visualizations(automaton) unless @options[:verify_output]
314
1044
  automaton
315
1045
  end
316
1046
 
1047
+ # @rbs (IR::Grammar grammar, IR::Automaton automaton) -> void
1048
+ def write_ielr_report(grammar, automaton)
1049
+ canonical = LALR::Builder.new(grammar, algorithm: :lr1).build
1050
+ @stdout.write(LALR::InadequacyReport.new(canonical, automaton).to_json)
1051
+ end
1052
+
317
1053
  # @rbs (IR::Automaton automaton, String input_path) -> void
318
1054
  def prepare_loaded_automaton(automaton, input_path)
319
1055
  report_conflicts(automaton, input_path)
320
- write_report(automaton, input_path) if @options[:verbose]
321
- write_visualizations(automaton)
1056
+ suggest_ielr(automaton, input_path)
1057
+ write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
1058
+ write_visualizations(automaton) unless @options[:verify_output]
322
1059
  end
323
1060
 
324
1061
  # @rbs (IR::Automaton automaton) -> void
325
1062
  def write_visualizations(automaton)
326
1063
  dot_path = @options[:dot]
1064
+ mermaid_path = @options[:mermaid]
327
1065
  html_path = @options[:html]
328
- File.write(dot_path, Codegen::Dot.render(automaton)) if dot_path
329
- File.write(html_path, Codegen::HTML.render(automaton)) if html_path
1066
+ if dot_path
1067
+ require_relative "codegen/dot"
1068
+ register_artifact(:dot, dot_path, Codegen::Dot.render(automaton))
1069
+ end
1070
+ if mermaid_path
1071
+ require_relative "codegen/mermaid"
1072
+ register_artifact(:mermaid, mermaid_path, Codegen::Mermaid.render(automaton))
1073
+ end
1074
+ return unless html_path
1075
+
1076
+ require_relative "codegen/html"
1077
+ register_artifact(:html, html_path, Codegen::HTML.render(automaton))
1078
+ end
1079
+
1080
+ # @rbs (IR::Grammar grammar) -> void
1081
+ def write_railroad(grammar)
1082
+ path = @options[:railroad]
1083
+ return unless path
1084
+
1085
+ require_relative "codegen/railroad"
1086
+ register_artifact(:railroad, path, Codegen::Railroad.render(grammar))
1087
+ end
1088
+
1089
+ # @rbs (String output_path) -> String
1090
+ def manifest_output_path_for(output_path)
1091
+ configured = @options[:manifest]
1092
+ return configured if configured.is_a?(String) && !configured.empty?
1093
+
1094
+ default_output_path(output_path, ".ibex.json")
330
1095
  end
331
1096
  end
1097
+ CONFIG_SUBCOMMAND_HELP = [
1098
+ " coverage collect, merge, or check runtime coverage",
1099
+ " config explain effective configuration without running user code"
1100
+ ].join("\n").freeze #: String
1101
+ autoload :CLIConfig, File.join(CLI_FEATURE_ROOT, "config")
332
1102
  # rubocop:enable Metrics/ClassLength
333
1103
  end