ibex 0.2.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 (437) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +95 -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 +262 -0
  40. data/docs/development.md +179 -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 +1581 -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/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 +5 -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 +11 -0
  63. data/docs/release-readiness.md +126 -68
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +144 -34
  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/lib/ibex/analysis/digraph.rb +128 -0
  75. data/lib/ibex/analysis.rb +1 -0
  76. data/lib/ibex/bison_import/importer.rb +516 -0
  77. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  78. data/lib/ibex/bison_import.rb +200 -0
  79. data/lib/ibex/bounded_subprocess.rb +171 -0
  80. data/lib/ibex/cli/ambiguity.rb +14 -5
  81. data/lib/ibex/cli/analysis.rb +134 -0
  82. data/lib/ibex/cli/bison_import.rb +121 -0
  83. data/lib/ibex/cli/config.rb +128 -0
  84. data/lib/ibex/cli/diagnostics.rb +31 -11
  85. data/lib/ibex/cli/documentation.rb +11 -8
  86. data/lib/ibex/cli/equiv.rb +169 -0
  87. data/lib/ibex/cli/error_messages.rb +11 -4
  88. data/lib/ibex/cli/explain.rb +14 -5
  89. data/lib/ibex/cli/fix.rb +203 -0
  90. data/lib/ibex/cli/formatting.rb +11 -10
  91. data/lib/ibex/cli/fuzz.rb +200 -0
  92. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  93. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  94. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  95. data/lib/ibex/cli/grammar_tests.rb +32 -14
  96. data/lib/ibex/cli/ir_tools.rb +5 -61
  97. data/lib/ibex/cli/outputs.rb +60 -33
  98. data/lib/ibex/cli/reduce.rb +202 -0
  99. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  100. data/lib/ibex/cli/samples.rb +49 -23
  101. data/lib/ibex/cli/verify.rb +116 -0
  102. data/lib/ibex/cli/watch.rb +6 -5
  103. data/lib/ibex/cli.rb +357 -56
  104. data/lib/ibex/codegen/action_locations.rb +1 -1
  105. data/lib/ibex/codegen/action_method_source.rb +5 -6
  106. data/lib/ibex/codegen/action_source.rb +3 -2
  107. data/lib/ibex/codegen/ambiguity.rb +5 -4
  108. data/lib/ibex/codegen/explain.rb +123 -55
  109. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  110. data/lib/ibex/codegen/rbs.rb +15 -7
  111. data/lib/ibex/codegen/report.rb +25 -13
  112. data/lib/ibex/codegen/ruby.rb +28 -64
  113. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  114. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  115. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  116. data/lib/ibex/configuration/explanation.rb +421 -0
  117. data/lib/ibex/configuration/inspector.rb +232 -0
  118. data/lib/ibex/configuration.rb +580 -0
  119. data/lib/ibex/coverage/collector.rb +21 -11
  120. data/lib/ibex/coverage/event_stream.rb +13 -7
  121. data/lib/ibex/coverage/report.rb +26 -18
  122. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  123. data/lib/ibex/delta_reducer.rb +99 -0
  124. data/lib/ibex/diff.rb +140 -0
  125. data/lib/ibex/equiv/machine.rb +135 -0
  126. data/lib/ibex/equiv.rb +373 -0
  127. data/lib/ibex/fix.rb +557 -0
  128. data/lib/ibex/frontend/ast.rb +22 -2
  129. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  130. data/lib/ibex/frontend/diagnostic.rb +1 -1
  131. data/lib/ibex/frontend/formatter.rb +72 -26
  132. data/lib/ibex/frontend/generated_parser.rb +147 -117
  133. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  134. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  135. data/lib/ibex/frontend/generation.rb +1 -1
  136. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  137. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  138. data/lib/ibex/frontend/regenerator.rb +1 -0
  139. data/lib/ibex/frontend/resolution.rb +2 -1
  140. data/lib/ibex/frontend/resolver.rb +1 -1
  141. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  142. data/lib/ibex/frontend/source_cursor.rb +2 -2
  143. data/lib/ibex/frontend/source_span.rb +9 -1
  144. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  145. data/lib/ibex/frontend/token_adapter.rb +10 -0
  146. data/lib/ibex/frontend.rb +1 -0
  147. data/lib/ibex/fuzz.rb +219 -0
  148. data/lib/ibex/generation_input.rb +1 -1
  149. data/lib/ibex/generation_manifest.rb +36 -25
  150. data/lib/ibex/generation_transaction.rb +1 -1
  151. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  152. data/lib/ibex/generation_transaction_validation.rb +2 -2
  153. data/lib/ibex/grammar_tests.rb +1 -1
  154. data/lib/ibex/ir/automaton_ir.rb +61 -19
  155. data/lib/ibex/ir/grammar_ir.rb +76 -45
  156. data/lib/ibex/ir/lexer_ir.rb +5 -5
  157. data/lib/ibex/ir/parser_contract.rb +135 -0
  158. data/lib/ibex/ir/serialize.rb +203 -76
  159. data/lib/ibex/ir/validator/automaton.rb +64 -34
  160. data/lib/ibex/ir/validator/base.rb +51 -33
  161. data/lib/ibex/ir/validator/grammar.rb +113 -83
  162. data/lib/ibex/ir/validator/lexer.rb +2 -2
  163. data/lib/ibex/ir/validator.rb +2 -1
  164. data/lib/ibex/ir.rb +16 -3
  165. data/lib/ibex/lalr/build_metrics.rb +50 -2
  166. data/lib/ibex/lalr/builder.rb +191 -56
  167. data/lib/ibex/lalr/conflict_search.rb +15 -4
  168. data/lib/ibex/lalr/counterexample.rb +19 -14
  169. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  170. data/lib/ibex/lalr/goto_follows.rb +229 -0
  171. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  172. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  173. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  174. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  175. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  176. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  177. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  178. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  179. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  180. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  181. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  182. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  183. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  184. data/lib/ibex/lalr.rb +52 -1
  185. data/lib/ibex/location.rb +3 -3
  186. data/lib/ibex/lsp/document_handlers.rb +9 -7
  187. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  188. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  189. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  190. data/lib/ibex/lsp/position_codec.rb +6 -2
  191. data/lib/ibex/lsp/request_handlers.rb +3 -2
  192. data/lib/ibex/lsp/request_support.rb +14 -7
  193. data/lib/ibex/lsp/server.rb +13 -11
  194. data/lib/ibex/lsp/symbol_index.rb +13 -13
  195. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  196. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  197. data/lib/ibex/lsp/transport.rb +13 -3
  198. data/lib/ibex/lsp/workspace.rb +5 -4
  199. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  200. data/lib/ibex/lsp.rb +1 -0
  201. data/lib/ibex/messages/en.yml +28 -0
  202. data/lib/ibex/messages/ja.yml +28 -0
  203. data/lib/ibex/messages.rb +68 -0
  204. data/lib/ibex/metrics.rb +175 -0
  205. data/lib/ibex/normalize/declarations.rb +2 -0
  206. data/lib/ibex/normalize/diagnostics.rb +63 -38
  207. data/lib/ibex/normalize/expander.rb +5 -4
  208. data/lib/ibex/normalize/expression.rb +8 -6
  209. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  210. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  211. data/lib/ibex/normalize/lexer.rb +3 -3
  212. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  213. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  214. data/lib/ibex/normalize/parameters.rb +31 -4
  215. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  216. data/lib/ibex/normalize.rb +10 -20
  217. data/lib/ibex/racc_migration/report.rb +6 -3
  218. data/lib/ibex/rake_task.rb +1 -1
  219. data/lib/ibex/samples.rb +43 -13
  220. data/lib/ibex/table_artifact/builder.rb +266 -0
  221. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  222. data/lib/ibex/table_artifact/document.rb +39 -0
  223. data/lib/ibex/table_artifact/executor.rb +187 -0
  224. data/lib/ibex/table_artifact/serializer.rb +57 -0
  225. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  226. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  227. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  228. data/lib/ibex/table_artifact/validator.rb +253 -0
  229. data/lib/ibex/table_artifact.rb +88 -0
  230. data/lib/ibex/table_simulation/result.rb +6 -2
  231. data/lib/ibex/table_simulation/step.rb +3 -1
  232. data/lib/ibex/tables.rb +14 -0
  233. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  234. data/lib/ibex/verification_report/builder.rb +159 -0
  235. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  236. data/lib/ibex/verification_report/logical_path.rb +68 -0
  237. data/lib/ibex/verification_report/validator.rb +329 -0
  238. data/lib/ibex/verification_report.rb +69 -0
  239. data/lib/ibex/verify/action_correspondence.rb +138 -0
  240. data/lib/ibex/verify/language_witness.rb +295 -0
  241. data/lib/ibex/verify/reference_collection.rb +190 -0
  242. data/lib/ibex/verify/result.rb +73 -0
  243. data/lib/ibex/verify/verifier.rb +598 -0
  244. data/lib/ibex/verify.rb +15 -0
  245. data/lib/ibex/version.rb +1 -1
  246. data/lib/ibex/watch/runner.rb +10 -1
  247. data/lib/ibex/watch/source_snapshot.rb +13 -8
  248. data/lib/ibex.rb +10 -0
  249. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  250. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  251. data/schema/bison-import-v1.schema.json +106 -0
  252. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  253. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  254. data/schema/construction-profile-v1.schema.json +779 -0
  255. data/schema/diff-v1.schema.json +41 -0
  256. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  257. data/schema/equiv-v1.schema.json +64 -0
  258. data/schema/error-ux-review-v1.schema.json +609 -0
  259. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  260. data/schema/error-ux-round2-v1.schema.json +519 -0
  261. data/schema/explain-v1.schema.json +63 -1
  262. data/schema/fix-v1.schema.json +64 -0
  263. data/schema/fix-v2.schema.json +66 -0
  264. data/schema/fix-v3.schema.json +101 -0
  265. data/schema/fuzz-regression-v1.schema.json +79 -0
  266. data/schema/fuzz-v1.schema.json +114 -0
  267. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  268. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  269. data/schema/grammar-ir.schema.json +271 -0
  270. data/schema/ielr-benchmark-v1.schema.json +85 -0
  271. data/schema/lexer-profile-v1.schema.json +491 -0
  272. data/schema/metrics-v1.schema.json +60 -0
  273. data/schema/reduce-v1.schema.json +22 -0
  274. data/schema/reduce-v2.schema.json +63 -0
  275. data/schema/table-artifact-v1.schema.json +377 -0
  276. data/schema/verification-report-v1.schema.json +222 -0
  277. data/schema/verify-v1.schema.json +42 -0
  278. data/sig/ibex/analysis/digraph.rbs +19 -0
  279. data/sig/ibex/bison_import/importer.rbs +140 -0
  280. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  281. data/sig/ibex/bison_import.rbs +95 -0
  282. data/sig/ibex/bounded_subprocess.rbs +76 -0
  283. data/sig/ibex/cli/ambiguity.rbs +6 -0
  284. data/sig/ibex/cli/analysis.rbs +43 -0
  285. data/sig/ibex/cli/bison_import.rbs +32 -0
  286. data/sig/ibex/cli/config.rbs +35 -0
  287. data/sig/ibex/cli/diagnostics.rbs +16 -3
  288. data/sig/ibex/cli/documentation.rbs +5 -1
  289. data/sig/ibex/cli/equiv.rbs +49 -0
  290. data/sig/ibex/cli/error_messages.rbs +6 -0
  291. data/sig/ibex/cli/explain.rbs +6 -0
  292. data/sig/ibex/cli/fix.rbs +53 -0
  293. data/sig/ibex/cli/formatting.rbs +5 -1
  294. data/sig/ibex/cli/fuzz.rbs +47 -0
  295. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  296. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  297. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  298. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  299. data/sig/ibex/cli/ir_tools.rbs +7 -19
  300. data/sig/ibex/cli/outputs.rbs +9 -3
  301. data/sig/ibex/cli/reduce.rbs +60 -0
  302. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  303. data/sig/ibex/cli/samples.rbs +8 -0
  304. data/sig/ibex/cli/verify.rbs +28 -0
  305. data/sig/ibex/cli/watch.rbs +2 -2
  306. data/sig/ibex/cli.rbs +79 -5
  307. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  308. data/sig/ibex/codegen/explain.rbs +45 -25
  309. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  310. data/sig/ibex/codegen/rbs.rbs +12 -10
  311. data/sig/ibex/codegen/report.rbs +12 -12
  312. data/sig/ibex/codegen/ruby.rbs +18 -15
  313. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  314. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  315. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  316. data/sig/ibex/configuration/explanation.rbs +195 -0
  317. data/sig/ibex/configuration/inspector.rbs +53 -0
  318. data/sig/ibex/configuration.rbs +242 -0
  319. data/sig/ibex/coverage/collector.rbs +16 -14
  320. data/sig/ibex/coverage/event_stream.rbs +12 -10
  321. data/sig/ibex/coverage/report.rbs +20 -18
  322. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  323. data/sig/ibex/delta_reducer.rbs +38 -0
  324. data/sig/ibex/diff.rbs +47 -0
  325. data/sig/ibex/equiv/machine.rbs +54 -0
  326. data/sig/ibex/equiv.rbs +129 -0
  327. data/sig/ibex/fix.rbs +138 -0
  328. data/sig/ibex/frontend/ast.rbs +33 -7
  329. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  330. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  331. data/sig/ibex/frontend/formatter.rbs +38 -30
  332. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  333. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  334. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  335. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  336. data/sig/ibex/frontend/resolution.rbs +3 -2
  337. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  338. data/sig/ibex/frontend/source_span.rbs +5 -2
  339. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  340. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  341. data/sig/ibex/fuzz.rbs +71 -0
  342. data/sig/ibex/generation_input.rbs +2 -2
  343. data/sig/ibex/generation_manifest.rbs +35 -32
  344. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  345. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  346. data/sig/ibex/grammar_tests.rbs +2 -2
  347. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  348. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  349. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  350. data/sig/ibex/ir/parser_contract.rbs +69 -0
  351. data/sig/ibex/ir/serialize.rbs +63 -28
  352. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  353. data/sig/ibex/ir/validator/base.rbs +35 -31
  354. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  355. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  356. data/sig/ibex/ir.rbs +9 -3
  357. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  358. data/sig/ibex/lalr/builder.rbs +75 -24
  359. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  360. data/sig/ibex/lalr/counterexample.rbs +17 -14
  361. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  362. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  363. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  364. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  365. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  366. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  367. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  368. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  369. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  370. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  371. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  372. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  373. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  374. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  375. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  376. data/sig/ibex/lalr.rbs +11 -1
  377. data/sig/ibex/location.rbs +6 -6
  378. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  379. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  380. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  381. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  382. data/sig/ibex/lsp/position_codec.rbs +8 -4
  383. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  384. data/sig/ibex/lsp/request_support.rbs +8 -8
  385. data/sig/ibex/lsp/server.rbs +16 -16
  386. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  387. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  388. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  389. data/sig/ibex/lsp/transport.rbs +18 -6
  390. data/sig/ibex/lsp/workspace.rbs +3 -2
  391. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  392. data/sig/ibex/messages.rbs +26 -0
  393. data/sig/ibex/metrics.rbs +50 -0
  394. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  395. data/sig/ibex/normalize/expander.rbs +2 -2
  396. data/sig/ibex/normalize/expression.rbs +10 -8
  397. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  398. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  399. data/sig/ibex/normalize/lexer.rbs +4 -4
  400. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  401. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  402. data/sig/ibex/normalize/parameters.rbs +13 -8
  403. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  404. data/sig/ibex/normalize.rbs +11 -5
  405. data/sig/ibex/racc_migration/report.rbs +8 -6
  406. data/sig/ibex/rake_task.rbs +1 -1
  407. data/sig/ibex/samples.rbs +13 -4
  408. data/sig/ibex/table_artifact/builder.rbs +93 -0
  409. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  410. data/sig/ibex/table_artifact/document.rbs +27 -0
  411. data/sig/ibex/table_artifact/executor.rbs +66 -0
  412. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  413. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  414. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  415. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  416. data/sig/ibex/table_artifact/validator.rbs +66 -0
  417. data/sig/ibex/table_artifact.rbs +32 -0
  418. data/sig/ibex/table_simulation/result.rbs +8 -4
  419. data/sig/ibex/table_simulation/step.rbs +4 -2
  420. data/sig/ibex/tables.rbs +10 -9
  421. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  422. data/sig/ibex/verification_report/builder.rbs +53 -0
  423. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  424. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  425. data/sig/ibex/verification_report/validator.rbs +114 -0
  426. data/sig/ibex/verification_report.rbs +44 -0
  427. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  428. data/sig/ibex/verify/language_witness.rbs +112 -0
  429. data/sig/ibex/verify/reference_collection.rbs +63 -0
  430. data/sig/ibex/verify/result.rbs +52 -0
  431. data/sig/ibex/verify/verifier.rbs +170 -0
  432. data/sig/ibex/verify.rbs +6 -0
  433. data/sig/ibex/watch/runner.rbs +12 -2
  434. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  435. metadata +232 -12
  436. data/lib/ibex/ir/migration.rb +0 -120
  437. data/sig/ibex/ir/migration.rbs +0 -34
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../diff"
7
+ require_relative "../metrics"
8
+
9
+ module Ibex
10
+ # CLI entry points for deterministic grammar diff and metrics reports.
11
+ module CLIAnalysis
12
+ # @rbs!
13
+ # type analysis_options = Hash[Symbol, untyped]
14
+ # private def normalize_grammar_path: (String) -> IR::Grammar
15
+ # private def set_configuration_option: (Symbol, Object?) -> void
16
+ # private def local_configuration_value: (Hash[Symbol, untyped], String) -> untyped
17
+ # private def set_local_configuration_option: (Hash[Symbol, untyped], Symbol, untyped) -> void
18
+
19
+ private
20
+
21
+ # @rbs (Array[String] arguments) -> Integer
22
+ def run_diff_command(arguments)
23
+ settings = analysis_options(arguments, "diff", operands: "OLD NEW")
24
+ if settings[:help]
25
+ @stdout.puts(settings.fetch(:help))
26
+ return 0
27
+ end
28
+ paths = settings.fetch(:paths)
29
+ raise Ibex::Error, "(diff):1:1: diff requires exactly two grammar or IR files" unless paths.length == 2
30
+
31
+ algorithm = settings.fetch(:algorithm)
32
+ before = load_analysis_automaton(paths.fetch(0), algorithm, explicit: algo_set?(settings))
33
+ after = load_analysis_automaton(paths.fetch(1), algorithm, explicit: algo_set?(settings))
34
+ report = Diff.new(before, after).to_h
35
+ write_analysis_report(report, settings.fetch(:format))
36
+ 0
37
+ end
38
+
39
+ # @rbs (Array[String] arguments) -> Integer
40
+ def run_metrics_command(arguments)
41
+ settings = analysis_options(arguments, "metrics", operands: "GRAMMAR")
42
+ if settings[:help]
43
+ @stdout.puts(settings.fetch(:help))
44
+ return 0
45
+ end
46
+ paths = settings.fetch(:paths)
47
+ raise Ibex::Error, "(metrics):1:1: metrics requires exactly one grammar or IR file" unless paths.length == 1
48
+
49
+ automaton = load_analysis_automaton(paths.fetch(0), settings.fetch(:algorithm), explicit: algo_set?(settings))
50
+ report = Metrics.new(automaton).to_h
51
+ write_analysis_report(report, settings.fetch(:format))
52
+ 0
53
+ end
54
+
55
+ # @rbs (Array[String] arguments, String command, operands: String) -> Hash[Symbol, untyped]
56
+ def analysis_options(arguments, command, operands:)
57
+ settings = {
58
+ paths: [], algorithm: Configuration::Registry.fetch("parser.algorithm").default,
59
+ mode: Configuration::Registry.fetch("grammar.mode").default, format: "json", configuration_explicit: []
60
+ } #: Hash[Symbol, untyped]
61
+ parser = OptionParser.new do |options|
62
+ options.banner = "Usage: ibex #{command} [options] #{operands}"
63
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "algorithm for grammar inputs") do |value|
64
+ set_local_configuration_option(settings, :algorithm, value.to_sym)
65
+ end
66
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
67
+ set_local_configuration_option(settings, :mode, value.to_sym)
68
+ set_configuration_option(:mode, value.to_sym)
69
+ end
70
+ options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
71
+ options.on("--help", "show help") { settings[:help] = options.to_s }
72
+ end
73
+ settings[:paths] = parser.parse(arguments)
74
+ settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
75
+ settings
76
+ end
77
+
78
+ # @rbs (String path, Symbol algorithm, explicit: bool) -> IR::Automaton
79
+ def load_analysis_automaton(path, algorithm, explicit:)
80
+ source = File.binread(path)
81
+ if source.lstrip.start_with?("{")
82
+ value = IR::Validator.validate(source)
83
+ if value.is_a?(IR::Automaton)
84
+ raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with Automaton IR analysis input" if explicit
85
+
86
+ return value
87
+ end
88
+ return build_analysis_automaton(value, algorithm, explicit) if value.is_a?(IR::Grammar)
89
+
90
+ raise Ibex::Error, "#{path}:1:1: analysis does not accept Lexer IR"
91
+ end
92
+
93
+ build_analysis_automaton(normalize_grammar_path(path), algorithm, explicit)
94
+ end
95
+
96
+ # @rbs (IR::Grammar grammar, Symbol algorithm, bool algorithm_explicit) -> IR::Automaton
97
+ def build_analysis_automaton(grammar, algorithm, algorithm_explicit)
98
+ explicit_keys = algorithm_explicit ? [:algorithm] : [] #: Array[Symbol]
99
+ active = activate_analysis_grammar(
100
+ grammar, options: { algorithm: algorithm }, explicit_keys: explicit_keys
101
+ )
102
+ LALR::Builder.new(
103
+ active, algorithm: configuration_value("parser.algorithm"),
104
+ entry_isolation: configuration_value("parser.entries") == :isolated
105
+ ).build
106
+ end
107
+
108
+ # @rbs (Hash[Symbol, untyped] report, String format) -> void
109
+ def write_analysis_report(report, format)
110
+ if format == "json"
111
+ @stdout.puts(JSON.pretty_generate(report))
112
+ elsif report.fetch(:ibex_report) == "metrics"
113
+ @stdout.puts("states=#{report.dig(:automaton, :states)} rules=#{report.dig(:grammar, :rules)} " \
114
+ "alternatives=#{report.dig(:grammar, :alternatives)}")
115
+ else
116
+ %i[symbols rules conflicts warnings].each do |section|
117
+ values = report.fetch(section)
118
+ @stdout.puts("#{section}: +#{values.fetch(:added).length} " \
119
+ "-#{values.fetch(:removed).length} ~#{values.fetch(:changed).length}")
120
+ end
121
+ end
122
+ end
123
+
124
+ # @rbs (Hash[Symbol, untyped] settings) -> bool
125
+ def algo_set?(settings)
126
+ settings.fetch(:configuration_explicit).include?(:algorithm)
127
+ end
128
+
129
+ # @rbs!
130
+ # private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, untyped],
131
+ # ?explicit_keys: Array[Symbol]) -> IR::Grammar
132
+ # private def configuration_value: (String) -> untyped
133
+ end
134
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../bison_import"
7
+
8
+ module Ibex
9
+ # CLI boundary for analysis-only Bison grammar import.
10
+ module CLIBisonImport
11
+ # @rbs!
12
+ # type bison_import_options = {
13
+ # format: String,
14
+ # max_bytes: Integer,
15
+ # max_tokens: Integer,
16
+ # max_rules: Integer,
17
+ # max_actions: Integer,
18
+ # ?output: String,
19
+ # ?class_name: String,
20
+ # ?help: bool
21
+ # }
22
+ # private def same_file_target?: (String, String) -> bool
23
+ # private def atomic_write_ir: (String, String) -> void
24
+
25
+ private
26
+
27
+ # @rbs (Array[String] arguments) -> Integer
28
+ def run_bison_import_command(arguments)
29
+ kind = arguments.shift
30
+ raise Ibex::Error, "(bison-import):1:1: import requires the `bison` format" unless kind == "bison"
31
+
32
+ settings = default_bison_import_options
33
+ parser = bison_import_option_parser(settings)
34
+ paths = parser.parse(arguments)
35
+ if settings[:help]
36
+ @stdout.puts(parser)
37
+ return 0
38
+ end
39
+ raise Ibex::Error, "(bison-import):1:1: import bison requires exactly one source file" unless paths.length == 1
40
+
41
+ path = paths.fetch(0)
42
+ result = build_bison_import(path, settings)
43
+ rendered = settings.fetch(:format) == "json" ? "#{JSON.pretty_generate(result.to_h)}\n" : result.source
44
+ write_bison_import(path, settings[:output], rendered)
45
+ 0
46
+ rescue BisonImport::BudgetExceeded => e
47
+ @stdout.puts(JSON.pretty_generate({ ibex_report: "bison_import", schema_version: 1 }.merge(e.details)))
48
+ 2
49
+ end
50
+
51
+ # @rbs () -> bison_import_options
52
+ def default_bison_import_options
53
+ {
54
+ format: "source",
55
+ max_bytes: BisonImport::Importer::DEFAULT_MAX_BYTES,
56
+ max_tokens: BisonImport::Importer::DEFAULT_MAX_TOKENS,
57
+ max_rules: BisonImport::Importer::DEFAULT_MAX_RULES,
58
+ max_actions: BisonImport::Importer::DEFAULT_MAX_ACTIONS
59
+ }
60
+ end
61
+
62
+ # @rbs (bison_import_options settings) -> OptionParser
63
+ def bison_import_option_parser(settings)
64
+ OptionParser.new do |options|
65
+ options.banner = "Usage: ibex import bison [options] grammar.y"
66
+ options.on("--format=FORMAT", %w[source json], "converted source or JSON report") do |value|
67
+ settings[:format] = value
68
+ end
69
+ options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
70
+ options.on("--class=NAME", "generated analysis grammar class") { |value| settings[:class_name] = value }
71
+ add_bison_import_budgets(options, settings)
72
+ options.on("--help", "show help") { settings[:help] = true }
73
+ end
74
+ end
75
+
76
+ # @rbs (OptionParser options, bison_import_options settings) -> void
77
+ def add_bison_import_budgets(options, settings)
78
+ {
79
+ "max-bytes" => :max_bytes,
80
+ "max-tokens" => :max_tokens,
81
+ "max-rules" => :max_rules,
82
+ "max-actions" => :max_actions
83
+ }.each do |name, key|
84
+ options.on("--#{name}=N", Integer, "positive import budget") do |value|
85
+ raise OptionParser::InvalidArgument, "--#{name} must be positive" unless value.positive?
86
+
87
+ settings[key] = value
88
+ end
89
+ end
90
+ end
91
+
92
+ # @rbs (String path, bison_import_options settings) -> BisonImport::Result
93
+ def build_bison_import(path, settings)
94
+ BisonImport::Importer.new(
95
+ File.binread(path),
96
+ file: path,
97
+ class_name: settings[:class_name],
98
+ max_bytes: settings.fetch(:max_bytes),
99
+ max_tokens: settings.fetch(:max_tokens),
100
+ max_rules: settings.fetch(:max_rules),
101
+ max_actions: settings.fetch(:max_actions)
102
+ ).run
103
+ end
104
+
105
+ # @rbs (String input, String? output, String source) -> void
106
+ def write_bison_import(input, output, source)
107
+ unless output
108
+ @stdout.write(source)
109
+ return
110
+ end
111
+ if same_file_target?(input, output)
112
+ raise Ibex::Error, "(bison-import):1:1: import input and output paths must be distinct"
113
+ end
114
+ if File.symlink?(output) || (File.exist?(output) && File.stat(output).nlink > 1)
115
+ raise Ibex::Error, "(bison-import):1:1: output refuses symlink aliases and files with multiple hard links"
116
+ end
117
+
118
+ atomic_write_ir(output, source)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../configuration/inspector"
7
+ require_relative "../frontend/source_loader"
8
+ require_relative "../frontend/resolver"
9
+ require_relative "../ir/validator"
10
+
11
+ module Ibex
12
+ # Static effective-configuration reporting for source grammars and Grammar IR.
13
+ module CLIConfig
14
+ # @rbs!
15
+ # type config_options = {
16
+ # format: String,
17
+ # mode: Symbol,
18
+ # configuration_explicit: Array[Symbol],
19
+ # ?from: String,
20
+ # ?algorithm: Symbol,
21
+ # ?entry_isolation: bool,
22
+ # ?cst_trivia: Symbol,
23
+ # ?superclass: String,
24
+ # ?omit_actions: bool,
25
+ # ?help: String
26
+ # }
27
+ # private def input_path: (Array[String]) -> String
28
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
29
+
30
+ private
31
+
32
+ # @rbs (Array[String] arguments) -> Integer
33
+ def run_config_command(arguments)
34
+ settings, paths = config_options(arguments)
35
+ if settings[:help]
36
+ @stdout.puts(settings.fetch(:help))
37
+ return 0
38
+ end
39
+
40
+ path = input_path(paths)
41
+ input = load_configuration_input(path, settings)
42
+ cli_values = Configuration::CLIAdapter.new(
43
+ settings, explicit_keys: settings.fetch(:configuration_explicit)
44
+ ).configuration_values
45
+ report = Configuration::Report.new(input, cli: cli_values)
46
+ write_configuration_report(report, settings.fetch(:format))
47
+ return 0 if report.success?
48
+
49
+ report.conflicts.each do |conflict|
50
+ @stderr.puts(Messages.translate("cli.error", language: @language, detail: conflict.message))
51
+ end
52
+ 1
53
+ end
54
+
55
+ # @rbs (Array[String] arguments) -> [config_options, Array[String]]
56
+ def config_options(arguments)
57
+ settings = {
58
+ format: "text", mode: Configuration::Registry.fetch("grammar.mode").default,
59
+ configuration_explicit: []
60
+ } #: config_options
61
+ parser = OptionParser.new do |options|
62
+ options.banner = "Usage: ibex config [options] grammarfile"
63
+ add_config_options(options, settings)
64
+ end
65
+ [settings, parser.parse(arguments)]
66
+ end
67
+
68
+ # @rbs (OptionParser options, config_options settings) -> void
69
+ def add_config_options(options, settings)
70
+ options.on("--format=FORMAT", %w[text json], "text or json (default: text)") do |value|
71
+ settings[:format] = value
72
+ end
73
+ options.on("--from=FORMAT", %w[grammar-ir], "read validated Grammar IR JSON") do |value|
74
+ settings[:from] = value
75
+ end
76
+ add_config_contract_options(options, settings)
77
+ options.on("--help", "show help") { settings[:help] = options.to_s }
78
+ end
79
+
80
+ # @rbs (OptionParser options, config_options settings) -> void
81
+ def add_config_contract_options(options, settings)
82
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
83
+ set_local_configuration_option(settings, :mode, value.to_sym)
84
+ end
85
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
86
+ set_local_configuration_option(settings, :algorithm, value.to_sym)
87
+ end
88
+ options.on("--entry-isolation", "build independent state sets for each start symbol") do
89
+ set_local_configuration_option(settings, :entry_isolation, true)
90
+ end
91
+ options.on("--cst-trivia=POLICY", %w[leading balanced drop attach], "CST trivia policy") do |value|
92
+ set_local_configuration_option(settings, :cst_trivia, value.to_sym)
93
+ end
94
+ options.on("--superclass=CLASS", "request a parser superclass") do |value|
95
+ set_local_configuration_option(settings, :superclass, value)
96
+ end
97
+ options.on("--no-omit-actions", "generate implicit action methods") do
98
+ set_local_configuration_option(settings, :omit_actions, false)
99
+ end
100
+ end
101
+
102
+ # @rbs (String path, config_options settings) -> Configuration::Input
103
+ def load_configuration_input(path, settings)
104
+ return load_grammar_ir_configuration(path) if settings[:from] == "grammar-ir"
105
+
106
+ loader = Frontend::SourceLoader.new(record_reads: true)
107
+ resolver = Frontend::Resolver.new(path, mode: settings.fetch(:mode), loader: loader)
108
+ Configuration::Inspector.from_source(resolver.resolve)
109
+ end
110
+
111
+ # @rbs (String path) -> Configuration::Input
112
+ def load_grammar_ir_configuration(path)
113
+ value = IR::Validator.validate(File.binread(path))
114
+ raise Ibex::Error, "#{path}:1:1: expected grammar-ir input" unless value.is_a?(IR::Grammar)
115
+
116
+ Configuration::Inspector.from_grammar_ir(value, path: path)
117
+ end
118
+
119
+ # @rbs (Configuration::Report report, String format) -> void
120
+ def write_configuration_report(report, format)
121
+ if format == "json"
122
+ @stdout.puts(JSON.pretty_generate(report.to_h))
123
+ else
124
+ @stdout.write(report.render_text)
125
+ end
126
+ end
127
+ end
128
+ end
@@ -11,24 +11,23 @@ module Ibex
11
11
  DEFAULT_MAX_DIAGNOSTICS = 20 #: Integer
12
12
 
13
13
  # @rbs!
14
- # type diagnostic_settings = {
15
- # format: String,
16
- # max_diagnostics: Integer,
17
- # mode: Symbol,
18
- # ?help: bool
19
- # }
14
+ # type diagnostic_settings = Hash[Symbol, untyped]
20
15
  #
21
16
  # private def input_path: (Array[String]) -> String
17
+ # private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
18
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
22
19
 
23
20
  private
24
21
 
25
22
  # @rbs (Array[String] arguments) -> Integer
26
23
  def run_diagnose_command(arguments)
27
24
  settings = {
28
- format: "text", max_diagnostics: DEFAULT_MAX_DIAGNOSTICS, mode: :default
25
+ format: "text", max_diagnostics: DEFAULT_MAX_DIAGNOSTICS,
26
+ mode: Configuration::Registry.fetch("grammar.mode").default, configuration_explicit: []
29
27
  } #: diagnostic_settings
30
28
  parser = diagnostics_option_parser(settings)
31
29
  remaining = parser.parse(arguments)
30
+ settings[:mode] = local_configuration_value(settings, "grammar.mode")
32
31
  if settings[:help]
33
32
  @stdout.puts(parser)
34
33
  return 0
@@ -78,7 +77,9 @@ module Ibex
78
77
  options.on("--max-diagnostics=N", "positive diagnostic limit") do |value|
79
78
  settings[:max_diagnostics] = positive_diagnostic_limit(value)
80
79
  end
81
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
80
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
81
+ set_local_configuration_option(settings, :mode, value.to_sym)
82
+ end
82
83
  options.on("--help", "show help") { settings[:help] = true }
83
84
  end
84
85
  end
@@ -98,19 +99,38 @@ module Ibex
98
99
  if format == "json"
99
100
  @stdout.puts(JSON.pretty_generate(diagnostics_document(result)))
100
101
  else
101
- result.diagnostics.each { |diagnostic| @stdout.puts(diagnostic.to_s) }
102
+ result.diagnostics.each { |diagnostic| @stdout.puts(localized_diagnostic_text(diagnostic)) }
102
103
  end
103
104
  end
104
105
 
105
- # @rbs (Frontend::ParseResult result) -> Hash[String, untyped]
106
+ # @rbs (Frontend::ParseResult result) -> Hash[String, Object?]
106
107
  def diagnostics_document(result)
107
108
  {
108
109
  "ibex_diagnostics" => "frontend",
109
110
  "schema_version" => DIAGNOSTICS_SCHEMA_VERSION,
110
111
  "success" => result.success?,
111
112
  "ast_available" => !result.ast.nil?,
112
- "diagnostics" => result.diagnostics.map(&:to_h)
113
+ "diagnostics" => result.diagnostics.map { |diagnostic| localized_diagnostic_hash(diagnostic) }
113
114
  }
114
115
  end
116
+
117
+ # @rbs (Frontend::Diagnostic diagnostic) -> String
118
+ def localized_diagnostic_text(diagnostic)
119
+ return diagnostic.to_s if @language == "en"
120
+
121
+ "#{diagnostic.location}: #{localized_diagnostic_message(diagnostic)}"
122
+ end
123
+
124
+ # @rbs (Frontend::Diagnostic diagnostic) -> Hash[Symbol, Object?]
125
+ def localized_diagnostic_hash(diagnostic)
126
+ diagnostic.to_h.merge(message: localized_diagnostic_message(diagnostic))
127
+ end
128
+
129
+ # @rbs (Frontend::Diagnostic diagnostic) -> String
130
+ def localized_diagnostic_message(diagnostic)
131
+ id = "diagnostic.#{diagnostic.code}"
132
+ id = "diagnostic.generic" unless Messages.known?(id)
133
+ Messages.translate(id, language: @language, detail: diagnostic.message)
134
+ end
115
135
  end
116
136
  end
@@ -9,24 +9,25 @@ module Ibex
9
9
  # Generates standalone grammar documentation from the canonical include closure.
10
10
  module CLIDocumentation
11
11
  # @rbs!
12
- # type documentation_settings = {
13
- # format: String,
14
- # mode: Symbol,
15
- # ?output: String,
16
- # ?help: bool
17
- # }
12
+ # type documentation_settings = Hash[Symbol, untyped]
18
13
  #
19
14
  # private def input_path: (Array[String]) -> String
20
15
  # private def same_file_target?: (String left, String right) -> bool
21
16
  # private def atomic_write_ir: (String path, String source) -> void
17
+ # private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
18
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
22
19
 
23
20
  private
24
21
 
25
22
  # @rbs (Array[String] arguments) -> Integer
26
23
  def run_documentation_command(arguments)
27
- settings = { format: "markdown", mode: :default } #: documentation_settings
24
+ settings = {
25
+ format: "markdown", mode: Configuration::Registry.fetch("grammar.mode").default,
26
+ configuration_explicit: []
27
+ } #: documentation_settings
28
28
  parser = documentation_option_parser(settings)
29
29
  remaining = parser.parse(arguments)
30
+ settings[:mode] = local_configuration_value(settings, "grammar.mode")
30
31
  if settings[:help]
31
32
  @stdout.puts(parser)
32
33
  return 0
@@ -59,7 +60,9 @@ module Ibex
59
60
  settings[:format] = value
60
61
  end
61
62
  options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
62
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
63
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
64
+ set_local_configuration_option(settings, :mode, value.to_sym)
65
+ end
63
66
  options.on("--help", "show help") { settings[:help] = true }
64
67
  end
65
68
  end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../equiv"
7
+
8
+ module Ibex
9
+ # CLI entry point for bounded language comparison.
10
+ module CLIEquiv
11
+ # @rbs!
12
+ # type equiv_options = Hash[Symbol, untyped]
13
+ # private def normalize_grammar_path: (String) -> IR::Grammar
14
+ # private def configuration_value: (String) -> untyped
15
+ # private def set_configuration_option: (Symbol, Object?) -> void
16
+ # private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
17
+ # private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
18
+
19
+ private
20
+
21
+ # @rbs (Array[String] arguments) -> Integer
22
+ def run_equiv_command(arguments)
23
+ settings = equiv_options(arguments)
24
+ if settings[:help]
25
+ @stdout.puts(settings.fetch(:help))
26
+ return 0
27
+ end
28
+ paths = settings.fetch(:paths)
29
+ raise Ibex::Error, "(equiv):1:1: equiv requires exactly two grammar or IR files" unless paths.length == 2
30
+
31
+ algorithm = settings.fetch(:algorithm)
32
+ left = load_equiv_automaton(paths.fetch(0), algorithm, explicit: algorithm_explicit_equiv?(settings))
33
+ right = load_equiv_automaton(paths.fetch(1), algorithm, explicit: algorithm_explicit_equiv?(settings))
34
+ comparison = Equiv.new(
35
+ left, right,
36
+ sample_count: settings.fetch(:sample_count), seed: settings.fetch(:seed),
37
+ max_tokens: settings.fetch(:max_tokens), max_configurations: settings.fetch(:max_configurations),
38
+ max_actions: settings.fetch(:max_actions), max_stack: settings.fetch(:max_stack),
39
+ rule_map: settings.fetch(:rule_map)
40
+ )
41
+ write_equiv_report(comparison.run, settings.fetch(:format))
42
+ 0
43
+ rescue Equiv::Difference => e
44
+ write_equiv_report(e.details, settings&.fetch(:format) || "json")
45
+ 1
46
+ rescue Equiv::BudgetExceeded => e
47
+ report = { ibex_report: "equiv", schema_version: 1 }.merge(e.details)
48
+ write_equiv_report(report, settings&.fetch(:format) || "json")
49
+ 2
50
+ end
51
+
52
+ # @rbs (Array[String] arguments) -> equiv_options
53
+ def equiv_options(arguments)
54
+ settings = default_equiv_options
55
+ parser = OptionParser.new do |options|
56
+ options.banner = "Usage: ibex equiv [options] LEFT RIGHT"
57
+ add_equiv_search_options(options, settings)
58
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "algorithm for grammar inputs") do |value|
59
+ set_local_configuration_option(settings, :algorithm, value.to_sym)
60
+ end
61
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
62
+ set_local_configuration_option(settings, :mode, value.to_sym)
63
+ set_configuration_option(:mode, value.to_sym)
64
+ end
65
+ options.on("--map=OLD=NEW", "declare a nonterminal correspondence; repeatable") do |value|
66
+ old_name, new_name = value.split("=", 2)
67
+ if old_name.nil? || old_name.empty? || new_name.nil? || new_name.empty?
68
+ raise OptionParser::InvalidArgument, "--map must be OLD=NEW"
69
+ end
70
+
71
+ settings.fetch(:rule_map)[old_name] = new_name
72
+ end
73
+ options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
74
+ options.on("--help", "show help") { settings[:help] = options.to_s }
75
+ end
76
+ settings[:paths] = parser.parse(arguments)
77
+ settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
78
+ settings
79
+ end
80
+
81
+ # @rbs () -> equiv_options
82
+ def default_equiv_options
83
+ {
84
+ paths: [], sample_count: 100, seed: 0, max_tokens: 8, max_configurations: 50_000,
85
+ max_actions: Equiv::DEFAULT_MAX_ACTIONS, max_stack: Equiv::DEFAULT_MAX_STACK,
86
+ algorithm: Configuration::Registry.fetch("parser.algorithm").default,
87
+ mode: Configuration::Registry.fetch("grammar.mode").default, format: "json", rule_map: {},
88
+ configuration_explicit: []
89
+ }
90
+ end
91
+
92
+ # @rbs (OptionParser options, equiv_options settings) -> void
93
+ def add_equiv_search_options(options, settings)
94
+ options.on("--samples=N", Integer, "samples generated in each direction") do |value|
95
+ settings[:sample_count] = positive_equiv_option(value, "samples")
96
+ end
97
+ options.on("--seed=N", Integer, "deterministic sampling seed") { |value| settings[:seed] = value }
98
+ options.on("--max-tokens=N", Integer, "maximum counterexample length") do |value|
99
+ settings[:max_tokens] = positive_equiv_option(value, "max-tokens")
100
+ end
101
+ options.on("--max-configurations=N", Integer, "product-state exploration budget") do |value|
102
+ settings[:max_configurations] = positive_equiv_option(value, "max-configurations")
103
+ end
104
+ options.on("--max-actions=N", Integer, "actions per simulated token sequence") do |value|
105
+ settings[:max_actions] = positive_equiv_option(value, "max-actions")
106
+ end
107
+ options.on("--max-stack=N", Integer, "simulated parser stack depth") do |value|
108
+ settings[:max_stack] = positive_equiv_option(value, "max-stack")
109
+ end
110
+ end
111
+
112
+ # @rbs (String path, Symbol algorithm, explicit: bool) -> IR::Automaton
113
+ def load_equiv_automaton(path, algorithm, explicit:)
114
+ source = File.binread(path)
115
+ if source.lstrip.start_with?("{")
116
+ value = IR::Validator.validate(source)
117
+ if value.is_a?(IR::Automaton)
118
+ raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with Automaton IR analysis input" if explicit
119
+
120
+ return value
121
+ end
122
+ return build_equiv_automaton(value, algorithm, explicit) if value.is_a?(IR::Grammar)
123
+
124
+ raise Ibex::Error, "#{path}:1:1: equiv does not accept Lexer IR"
125
+ end
126
+
127
+ build_equiv_automaton(normalize_grammar_path(path), algorithm, explicit)
128
+ end
129
+
130
+ # @rbs (IR::Grammar grammar, Symbol algorithm, bool algorithm_explicit) -> IR::Automaton
131
+ def build_equiv_automaton(grammar, algorithm, algorithm_explicit)
132
+ explicit_keys = algorithm_explicit ? [:algorithm] : [] #: Array[Symbol]
133
+ active = activate_analysis_grammar(
134
+ grammar, options: { algorithm: algorithm }, explicit_keys: explicit_keys
135
+ )
136
+ LALR::Builder.new(
137
+ active, algorithm: configuration_value("parser.algorithm"),
138
+ entry_isolation: configuration_value("parser.entries") == :isolated
139
+ ).build
140
+ end
141
+
142
+ # @rbs (Hash[Symbol, Object?] report, String format) -> void
143
+ def write_equiv_report(report, format)
144
+ if format == "json"
145
+ @stdout.puts(JSON.pretty_generate(report))
146
+ else
147
+ @stdout.puts("result=#{report.fetch(:result)}")
148
+ @stdout.puts("witness=#{report[:witness].inspect}") if report[:witness]
149
+ @stdout.puts(report.fetch(:statement))
150
+ end
151
+ end
152
+
153
+ # @rbs (equiv_options settings) -> bool
154
+ def algorithm_explicit_equiv?(settings)
155
+ settings.fetch(:configuration_explicit).include?(:algorithm)
156
+ end
157
+
158
+ # @rbs!
159
+ # private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
160
+ # ?explicit_keys: Array[Symbol]) -> IR::Grammar
161
+
162
+ # @rbs (Integer value, String name) -> Integer
163
+ def positive_equiv_option(value, name)
164
+ return value if value.positive?
165
+
166
+ raise OptionParser::InvalidArgument, "--#{name} must be positive"
167
+ end
168
+ end
169
+ end