ibex 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +100 -272
  3. data/docs/architecture.md +187 -35
  4. data/docs/bison-import.md +103 -0
  5. data/docs/comparison-policy.md +127 -0
  6. data/docs/configuration-model.md +59 -0
  7. data/docs/configuration-report.md +28 -0
  8. data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
  9. data/docs/conflict-explanation-study.md +62 -0
  10. data/docs/construction-profiling.md +157 -0
  11. data/docs/cst-migration.md +3 -4
  12. data/docs/cst.md +24 -0
  13. data/docs/decisions/0000-template.md +24 -0
  14. data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
  15. data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
  16. data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
  17. data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
  18. data/docs/decisions/0005-contained-grammar-composition.md +34 -0
  19. data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
  20. data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
  21. data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
  22. data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
  23. data/docs/decisions/0010-committed-runtime-observation.md +34 -0
  24. data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
  25. data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
  26. data/docs/decisions/0013-transactional-generation-publication.md +37 -0
  27. data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
  28. data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
  29. data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
  30. data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
  31. data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
  32. data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
  33. data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
  34. data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
  35. data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
  36. data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
  37. data/docs/decisions/0024-direct-ielr-construction.md +42 -0
  38. data/docs/decisions/README.md +74 -0
  39. data/docs/declarative-configuration.md +274 -0
  40. data/docs/development.md +196 -2
  41. data/docs/direct-ielr-decision.md +120 -0
  42. data/docs/direct-multi-entry-decision.md +82 -0
  43. data/docs/editor-setup.md +2 -1
  44. data/docs/error-ux-review-rubric-v1.md +117 -0
  45. data/docs/error-ux-reviews/v1/records/README.md +22 -0
  46. data/docs/error-ux-round2-review-status-v1.json +27 -0
  47. data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
  48. data/docs/error-ux-round2-v1.json +1617 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +54 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +184 -13
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/impact.md +101 -0
  56. data/docs/ja/bison-import.md +51 -0
  57. data/docs/ja/stable-api.md +53 -0
  58. data/docs/lexer-construction-profile.md +109 -0
  59. data/docs/lexer-migration.md +5 -0
  60. data/docs/maturity.md +129 -0
  61. data/docs/project-site-strategy.md +54 -0
  62. data/docs/racc-migration-evidence.md +45 -0
  63. data/docs/racc-migration.md +11 -0
  64. data/docs/release-readiness.md +126 -68
  65. data/docs/repair-semantics.md +123 -0
  66. data/docs/runtime-abi-evolution.md +310 -0
  67. data/docs/stability.md +145 -34
  68. data/docs/status.md +43 -0
  69. data/docs/syntax-sessions.md +183 -0
  70. data/docs/table-artifact.md +125 -0
  71. data/docs/test-interactions.md +219 -0
  72. data/docs/verification-report.md +137 -0
  73. data/docs/verifier-trust-boundary.md +199 -0
  74. data/docs/workloads.md +132 -0
  75. data/lib/ibex/analysis/digraph.rb +128 -0
  76. data/lib/ibex/analysis/sets.rb +14 -0
  77. data/lib/ibex/analysis.rb +1 -0
  78. data/lib/ibex/bison_import/importer.rb +516 -0
  79. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  80. data/lib/ibex/bison_import.rb +200 -0
  81. data/lib/ibex/bounded_subprocess.rb +171 -0
  82. data/lib/ibex/cli/ambiguity.rb +14 -5
  83. data/lib/ibex/cli/analysis.rb +134 -0
  84. data/lib/ibex/cli/bison_import.rb +121 -0
  85. data/lib/ibex/cli/config.rb +128 -0
  86. data/lib/ibex/cli/diagnostics.rb +31 -11
  87. data/lib/ibex/cli/documentation.rb +11 -8
  88. data/lib/ibex/cli/equiv.rb +169 -0
  89. data/lib/ibex/cli/error_messages.rb +11 -4
  90. data/lib/ibex/cli/explain.rb +14 -5
  91. data/lib/ibex/cli/fix.rb +203 -0
  92. data/lib/ibex/cli/formatting.rb +11 -10
  93. data/lib/ibex/cli/fuzz.rb +200 -0
  94. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  95. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  96. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  97. data/lib/ibex/cli/grammar_tests.rb +32 -14
  98. data/lib/ibex/cli/impact.rb +393 -0
  99. data/lib/ibex/cli/ir_tools.rb +5 -61
  100. data/lib/ibex/cli/outputs.rb +60 -33
  101. data/lib/ibex/cli/reduce.rb +202 -0
  102. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  103. data/lib/ibex/cli/samples.rb +49 -23
  104. data/lib/ibex/cli/verify.rb +116 -0
  105. data/lib/ibex/cli/watch.rb +6 -5
  106. data/lib/ibex/cli.rb +362 -56
  107. data/lib/ibex/codegen/action_locations.rb +1 -1
  108. data/lib/ibex/codegen/action_method_source.rb +5 -6
  109. data/lib/ibex/codegen/action_source.rb +3 -2
  110. data/lib/ibex/codegen/ambiguity.rb +5 -4
  111. data/lib/ibex/codegen/explain.rb +123 -55
  112. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  113. data/lib/ibex/codegen/rbs.rb +15 -7
  114. data/lib/ibex/codegen/report.rb +25 -13
  115. data/lib/ibex/codegen/ruby.rb +28 -64
  116. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  117. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  118. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  119. data/lib/ibex/configuration/explanation.rb +421 -0
  120. data/lib/ibex/configuration/inspector.rb +232 -0
  121. data/lib/ibex/configuration.rb +580 -0
  122. data/lib/ibex/coverage/collector.rb +21 -11
  123. data/lib/ibex/coverage/event_stream.rb +13 -7
  124. data/lib/ibex/coverage/report.rb +26 -18
  125. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  126. data/lib/ibex/delta_reducer.rb +99 -0
  127. data/lib/ibex/diff.rb +141 -0
  128. data/lib/ibex/equiv/machine.rb +135 -0
  129. data/lib/ibex/equiv.rb +373 -0
  130. data/lib/ibex/fix.rb +557 -0
  131. data/lib/ibex/frontend/ast.rb +22 -2
  132. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  133. data/lib/ibex/frontend/diagnostic.rb +1 -1
  134. data/lib/ibex/frontend/formatter.rb +72 -26
  135. data/lib/ibex/frontend/generated_parser.rb +147 -117
  136. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  137. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  138. data/lib/ibex/frontend/generation.rb +1 -1
  139. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  140. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  141. data/lib/ibex/frontend/regenerator.rb +1 -0
  142. data/lib/ibex/frontend/resolution.rb +2 -1
  143. data/lib/ibex/frontend/resolver.rb +1 -1
  144. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  145. data/lib/ibex/frontend/source_cursor.rb +2 -2
  146. data/lib/ibex/frontend/source_span.rb +9 -1
  147. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  148. data/lib/ibex/frontend/token_adapter.rb +10 -0
  149. data/lib/ibex/frontend.rb +1 -0
  150. data/lib/ibex/fuzz.rb +219 -0
  151. data/lib/ibex/generation_input.rb +1 -1
  152. data/lib/ibex/generation_manifest.rb +36 -25
  153. data/lib/ibex/generation_transaction.rb +1 -1
  154. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  155. data/lib/ibex/generation_transaction_validation.rb +2 -2
  156. data/lib/ibex/grammar_tests.rb +1 -1
  157. data/lib/ibex/impact/action_impact.rb +148 -0
  158. data/lib/ibex/impact/automaton_impact.rb +57 -0
  159. data/lib/ibex/impact/coverage_impact.rb +53 -0
  160. data/lib/ibex/impact/graph.rb +132 -0
  161. data/lib/ibex/impact/propagation.rb +147 -0
  162. data/lib/ibex/impact/report.rb +273 -0
  163. data/lib/ibex/impact/seeds.rb +69 -0
  164. data/lib/ibex/impact/severity.rb +139 -0
  165. data/lib/ibex/impact.rb +16 -0
  166. data/lib/ibex/ir/automaton_ir.rb +61 -19
  167. data/lib/ibex/ir/grammar_ir.rb +76 -45
  168. data/lib/ibex/ir/lexer_ir.rb +5 -5
  169. data/lib/ibex/ir/parser_contract.rb +135 -0
  170. data/lib/ibex/ir/serialize.rb +203 -76
  171. data/lib/ibex/ir/validator/automaton.rb +64 -34
  172. data/lib/ibex/ir/validator/base.rb +51 -33
  173. data/lib/ibex/ir/validator/grammar.rb +113 -83
  174. data/lib/ibex/ir/validator/lexer.rb +2 -2
  175. data/lib/ibex/ir/validator.rb +2 -1
  176. data/lib/ibex/ir.rb +16 -3
  177. data/lib/ibex/lalr/build_metrics.rb +50 -2
  178. data/lib/ibex/lalr/builder.rb +191 -56
  179. data/lib/ibex/lalr/conflict_search.rb +15 -4
  180. data/lib/ibex/lalr/counterexample.rb +19 -14
  181. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  182. data/lib/ibex/lalr/goto_follows.rb +229 -0
  183. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  184. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  185. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  186. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  187. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  188. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  189. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  190. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  191. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  192. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  193. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  194. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  195. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  196. data/lib/ibex/lalr.rb +52 -1
  197. data/lib/ibex/location.rb +3 -3
  198. data/lib/ibex/lsp/document_handlers.rb +9 -7
  199. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  200. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  201. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  202. data/lib/ibex/lsp/position_codec.rb +6 -2
  203. data/lib/ibex/lsp/request_handlers.rb +3 -2
  204. data/lib/ibex/lsp/request_support.rb +14 -7
  205. data/lib/ibex/lsp/server.rb +13 -11
  206. data/lib/ibex/lsp/symbol_index.rb +13 -13
  207. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  208. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  209. data/lib/ibex/lsp/transport.rb +13 -3
  210. data/lib/ibex/lsp/workspace.rb +5 -4
  211. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  212. data/lib/ibex/lsp.rb +1 -0
  213. data/lib/ibex/messages/en.yml +28 -0
  214. data/lib/ibex/messages/ja.yml +28 -0
  215. data/lib/ibex/messages.rb +68 -0
  216. data/lib/ibex/metrics.rb +175 -0
  217. data/lib/ibex/normalize/declarations.rb +2 -0
  218. data/lib/ibex/normalize/diagnostics.rb +63 -38
  219. data/lib/ibex/normalize/expander.rb +5 -4
  220. data/lib/ibex/normalize/expression.rb +8 -6
  221. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  222. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  223. data/lib/ibex/normalize/lexer.rb +3 -3
  224. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  225. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  226. data/lib/ibex/normalize/parameters.rb +31 -4
  227. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  228. data/lib/ibex/normalize.rb +10 -20
  229. data/lib/ibex/racc_migration/report.rb +6 -3
  230. data/lib/ibex/rake_task.rb +1 -1
  231. data/lib/ibex/samples.rb +43 -13
  232. data/lib/ibex/table_artifact/builder.rb +266 -0
  233. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  234. data/lib/ibex/table_artifact/document.rb +39 -0
  235. data/lib/ibex/table_artifact/executor.rb +187 -0
  236. data/lib/ibex/table_artifact/serializer.rb +57 -0
  237. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  238. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  239. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  240. data/lib/ibex/table_artifact/validator.rb +253 -0
  241. data/lib/ibex/table_artifact.rb +88 -0
  242. data/lib/ibex/table_simulation/result.rb +6 -2
  243. data/lib/ibex/table_simulation/step.rb +3 -1
  244. data/lib/ibex/tables.rb +14 -0
  245. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  246. data/lib/ibex/verification_report/builder.rb +159 -0
  247. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  248. data/lib/ibex/verification_report/logical_path.rb +68 -0
  249. data/lib/ibex/verification_report/validator.rb +329 -0
  250. data/lib/ibex/verification_report.rb +69 -0
  251. data/lib/ibex/verify/action_correspondence.rb +138 -0
  252. data/lib/ibex/verify/language_witness.rb +295 -0
  253. data/lib/ibex/verify/reference_collection.rb +190 -0
  254. data/lib/ibex/verify/result.rb +73 -0
  255. data/lib/ibex/verify/verifier.rb +598 -0
  256. data/lib/ibex/verify.rb +15 -0
  257. data/lib/ibex/version.rb +1 -1
  258. data/lib/ibex/watch/runner.rb +10 -1
  259. data/lib/ibex/watch/source_snapshot.rb +13 -8
  260. data/lib/ibex.rb +11 -0
  261. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  262. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  263. data/schema/bison-import-v1.schema.json +106 -0
  264. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  265. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  266. data/schema/construction-profile-v1.schema.json +779 -0
  267. data/schema/diff-v1.schema.json +41 -0
  268. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  269. data/schema/equiv-v1.schema.json +64 -0
  270. data/schema/error-ux-review-v1.schema.json +609 -0
  271. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  272. data/schema/error-ux-round2-v1.schema.json +519 -0
  273. data/schema/explain-v1.schema.json +63 -1
  274. data/schema/fix-v1.schema.json +64 -0
  275. data/schema/fix-v2.schema.json +66 -0
  276. data/schema/fix-v3.schema.json +101 -0
  277. data/schema/fuzz-regression-v1.schema.json +79 -0
  278. data/schema/fuzz-v1.schema.json +114 -0
  279. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  280. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  281. data/schema/grammar-ir.schema.json +271 -0
  282. data/schema/ielr-benchmark-v1.schema.json +85 -0
  283. data/schema/impact-v1.schema.json +208 -0
  284. data/schema/lexer-profile-v1.schema.json +491 -0
  285. data/schema/metrics-v1.schema.json +60 -0
  286. data/schema/reduce-v1.schema.json +22 -0
  287. data/schema/reduce-v2.schema.json +63 -0
  288. data/schema/table-artifact-v1.schema.json +377 -0
  289. data/schema/verification-report-v1.schema.json +222 -0
  290. data/schema/verify-v1.schema.json +42 -0
  291. data/sig/ibex/analysis/digraph.rbs +19 -0
  292. data/sig/ibex/analysis/sets.rbs +10 -0
  293. data/sig/ibex/bison_import/importer.rbs +140 -0
  294. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  295. data/sig/ibex/bison_import.rbs +95 -0
  296. data/sig/ibex/bounded_subprocess.rbs +76 -0
  297. data/sig/ibex/cli/ambiguity.rbs +6 -0
  298. data/sig/ibex/cli/analysis.rbs +43 -0
  299. data/sig/ibex/cli/bison_import.rbs +32 -0
  300. data/sig/ibex/cli/config.rbs +35 -0
  301. data/sig/ibex/cli/diagnostics.rbs +16 -3
  302. data/sig/ibex/cli/documentation.rbs +5 -1
  303. data/sig/ibex/cli/equiv.rbs +49 -0
  304. data/sig/ibex/cli/error_messages.rbs +6 -0
  305. data/sig/ibex/cli/explain.rbs +6 -0
  306. data/sig/ibex/cli/fix.rbs +53 -0
  307. data/sig/ibex/cli/formatting.rbs +5 -1
  308. data/sig/ibex/cli/fuzz.rbs +47 -0
  309. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  310. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  311. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  312. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  313. data/sig/ibex/cli/impact.rbs +95 -0
  314. data/sig/ibex/cli/ir_tools.rbs +7 -19
  315. data/sig/ibex/cli/outputs.rbs +9 -3
  316. data/sig/ibex/cli/reduce.rbs +60 -0
  317. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  318. data/sig/ibex/cli/samples.rbs +8 -0
  319. data/sig/ibex/cli/verify.rbs +28 -0
  320. data/sig/ibex/cli/watch.rbs +2 -2
  321. data/sig/ibex/cli.rbs +79 -5
  322. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  323. data/sig/ibex/codegen/explain.rbs +45 -25
  324. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  325. data/sig/ibex/codegen/rbs.rbs +12 -10
  326. data/sig/ibex/codegen/report.rbs +12 -12
  327. data/sig/ibex/codegen/ruby.rbs +18 -15
  328. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  329. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  330. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  331. data/sig/ibex/configuration/explanation.rbs +195 -0
  332. data/sig/ibex/configuration/inspector.rbs +53 -0
  333. data/sig/ibex/configuration.rbs +242 -0
  334. data/sig/ibex/coverage/collector.rbs +16 -14
  335. data/sig/ibex/coverage/event_stream.rbs +12 -10
  336. data/sig/ibex/coverage/report.rbs +20 -18
  337. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  338. data/sig/ibex/delta_reducer.rbs +38 -0
  339. data/sig/ibex/diff.rbs +47 -0
  340. data/sig/ibex/equiv/machine.rbs +54 -0
  341. data/sig/ibex/equiv.rbs +129 -0
  342. data/sig/ibex/fix.rbs +138 -0
  343. data/sig/ibex/frontend/ast.rbs +33 -7
  344. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  345. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  346. data/sig/ibex/frontend/formatter.rbs +38 -30
  347. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  348. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  349. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  350. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  351. data/sig/ibex/frontend/resolution.rbs +3 -2
  352. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  353. data/sig/ibex/frontend/source_span.rbs +5 -2
  354. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  355. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  356. data/sig/ibex/fuzz.rbs +71 -0
  357. data/sig/ibex/generation_input.rbs +2 -2
  358. data/sig/ibex/generation_manifest.rbs +35 -32
  359. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  360. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  361. data/sig/ibex/grammar_tests.rbs +2 -2
  362. data/sig/ibex/impact/action_impact.rbs +58 -0
  363. data/sig/ibex/impact/automaton_impact.rbs +37 -0
  364. data/sig/ibex/impact/coverage_impact.rbs +28 -0
  365. data/sig/ibex/impact/graph.rbs +67 -0
  366. data/sig/ibex/impact/propagation.rbs +56 -0
  367. data/sig/ibex/impact/report.rbs +107 -0
  368. data/sig/ibex/impact/seeds.rbs +36 -0
  369. data/sig/ibex/impact/severity.rbs +55 -0
  370. data/sig/ibex/impact.rbs +6 -0
  371. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  372. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  373. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  374. data/sig/ibex/ir/parser_contract.rbs +69 -0
  375. data/sig/ibex/ir/serialize.rbs +63 -28
  376. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  377. data/sig/ibex/ir/validator/base.rbs +35 -31
  378. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  379. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  380. data/sig/ibex/ir.rbs +9 -3
  381. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  382. data/sig/ibex/lalr/builder.rbs +75 -24
  383. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  384. data/sig/ibex/lalr/counterexample.rbs +17 -14
  385. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  386. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  387. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  388. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  389. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  390. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  391. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  392. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  393. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  394. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  395. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  396. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  397. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  398. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  399. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  400. data/sig/ibex/lalr.rbs +11 -1
  401. data/sig/ibex/location.rbs +6 -6
  402. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  403. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  404. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  405. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  406. data/sig/ibex/lsp/position_codec.rbs +8 -4
  407. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  408. data/sig/ibex/lsp/request_support.rbs +8 -8
  409. data/sig/ibex/lsp/server.rbs +16 -16
  410. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  411. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  412. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  413. data/sig/ibex/lsp/transport.rbs +18 -6
  414. data/sig/ibex/lsp/workspace.rbs +3 -2
  415. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  416. data/sig/ibex/messages.rbs +26 -0
  417. data/sig/ibex/metrics.rbs +50 -0
  418. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  419. data/sig/ibex/normalize/expander.rbs +2 -2
  420. data/sig/ibex/normalize/expression.rbs +10 -8
  421. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  422. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  423. data/sig/ibex/normalize/lexer.rbs +4 -4
  424. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  425. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  426. data/sig/ibex/normalize/parameters.rbs +13 -8
  427. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  428. data/sig/ibex/normalize.rbs +11 -5
  429. data/sig/ibex/racc_migration/report.rbs +8 -6
  430. data/sig/ibex/rake_task.rbs +1 -1
  431. data/sig/ibex/samples.rbs +13 -4
  432. data/sig/ibex/table_artifact/builder.rbs +93 -0
  433. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  434. data/sig/ibex/table_artifact/document.rbs +27 -0
  435. data/sig/ibex/table_artifact/executor.rbs +66 -0
  436. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  437. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  438. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  439. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  440. data/sig/ibex/table_artifact/validator.rbs +66 -0
  441. data/sig/ibex/table_artifact.rbs +32 -0
  442. data/sig/ibex/table_simulation/result.rbs +8 -4
  443. data/sig/ibex/table_simulation/step.rbs +4 -2
  444. data/sig/ibex/tables.rbs +10 -9
  445. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  446. data/sig/ibex/verification_report/builder.rbs +53 -0
  447. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  448. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  449. data/sig/ibex/verification_report/validator.rbs +114 -0
  450. data/sig/ibex/verification_report.rbs +44 -0
  451. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  452. data/sig/ibex/verify/language_witness.rbs +112 -0
  453. data/sig/ibex/verify/reference_collection.rbs +63 -0
  454. data/sig/ibex/verify/result.rbs +52 -0
  455. data/sig/ibex/verify/verifier.rbs +170 -0
  456. data/sig/ibex/verify.rbs +6 -0
  457. data/sig/ibex/watch/runner.rbs +12 -2
  458. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  459. metadata +254 -12
  460. data/lib/ibex/ir/migration.rb +0 -120
  461. data/sig/ibex/ir/migration.rbs +0 -34
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../diff"
7
+ require_relative "../impact"
8
+
9
+ module Ibex
10
+ # CLI adapter for potential and confirmed grammar impact reports.
11
+ # rubocop:disable Metrics/ModuleLength, Metrics/MethodLength, Metrics/AbcSize
12
+ module CLIImpact
13
+ include CLIAnalysis
14
+
15
+ private
16
+
17
+ # @rbs (Array[String] arguments) -> Integer
18
+ def run_impact_command(arguments)
19
+ settings = impact_option_parser(arguments)
20
+ if settings[:help]
21
+ @stdout.puts(settings.fetch(:help))
22
+ return 0
23
+ end
24
+ paths = settings.fetch(:paths)
25
+ unless [1, 2].include?(paths.length)
26
+ raise Ibex::Error, "(impact):1:1: impact requires one grammar or two grammar files"
27
+ end
28
+
29
+ extend CLIAnalysis unless singleton_class.ancestors.include?(CLIAnalysis)
30
+ settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
31
+ analysis = if paths.length == 1
32
+ potential_impact(paths, settings)
33
+ else
34
+ confirmed_impact(paths, settings)
35
+ end
36
+ report, identities, gate_report = analysis
37
+ apply_baseline(report, identities, settings)
38
+ apply_baseline(gate_report, identities, settings)
39
+ write_impact_report(report, settings.fetch(:format))
40
+ Impact::Severity.fails?(gate_report, settings.fetch(:fail_on)) ? 1 : 0
41
+ end
42
+
43
+ # @rbs (Array[String]) -> Hash[Symbol, untyped]
44
+ def impact_option_parser(arguments)
45
+ settings = {
46
+ paths: [], symbols: [], depth: nil, kinds: [:all], severity: "medium", coverage: [],
47
+ fail_on: [], format: "json", update_baseline: false, configuration_explicit: [],
48
+ algorithm: Configuration::Registry.fetch("parser.algorithm").default,
49
+ mode: Configuration::Registry.fetch("grammar.mode").default
50
+ } #: Hash[Symbol, untyped]
51
+ # rubocop:disable Metrics/BlockLength
52
+ parser = OptionParser.new do |options|
53
+ options.banner = "Usage: ibex impact [options] GRAMMAR or OLD NEW"
54
+ options.on("--symbol=NAME[,NAME]", "seed nonterminals for a one-version analysis") do |value|
55
+ settings[:symbols].concat(value.split(",").map(&:strip))
56
+ end
57
+ options.on("--depth=N", Integer, "maximum propagation depth") do |value|
58
+ raise OptionParser::InvalidArgument, "--depth must be non-negative" if value.negative?
59
+
60
+ settings[:depth] = value
61
+ end
62
+ options.on("--kind=LIST", "reference, first, or follow") { |value| settings[:kinds] = parse_kinds(value) }
63
+ options.on("--severity=LEVEL", Impact::Severity::LEVELS, "minimum severity") do |value|
64
+ settings[:severity] = value
65
+ end
66
+ options.on("--coverage=PATH", "runtime coverage report (repeatable)") { |value| settings[:coverage] << value }
67
+ options.on("--baseline=PATH", "known conflict baseline") { |value| settings[:baseline] = value }
68
+ options.on("--update-baseline", "write the current conflict baseline") { settings[:update_baseline] = true }
69
+ options.on("--fail-on=LIST", "CI gate conditions") do |value|
70
+ settings[:fail_on] = Impact::Severity.validate_gates(value.split(",").map(&:strip))
71
+ end
72
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "algorithm for grammar inputs") do |value|
73
+ set_local_configuration_option(settings, :algorithm, value.to_sym)
74
+ end
75
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
76
+ set_local_configuration_option(settings, :mode, value.to_sym)
77
+ set_configuration_option(:mode, value.to_sym)
78
+ end
79
+ options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
80
+ options.on("--help", "show help") { settings[:help] = options.to_s }
81
+ end
82
+ # rubocop:enable Metrics/BlockLength
83
+ settings[:paths] = parser.parse(arguments)
84
+ settings
85
+ end
86
+
87
+ # @rbs (String value) -> Array[Symbol]
88
+ def parse_kinds(value)
89
+ kinds = value.split(",").map(&:strip).map(&:to_sym)
90
+ unknown = kinds - %i[reference first follow]
91
+ raise OptionParser::InvalidArgument, "unknown impact kind #{unknown.first.inspect}" unless unknown.empty?
92
+
93
+ kinds.uniq
94
+ end
95
+
96
+ # @rbs (Array[String], Hash[Symbol, untyped]) -> [Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?]]
97
+ def potential_impact(paths, settings)
98
+ automaton = load_analysis_automaton(paths.fetch(0), settings.fetch(:algorithm), explicit: algo_set?(settings))
99
+ names = settings.fetch(:symbols)
100
+ raise Ibex::Error, "(impact):1:1: one-version impact requires --symbol" if names.empty?
101
+
102
+ seeds = Impact::Seeds.new(automaton.grammar, names)
103
+ graph = Impact::Graph.new(automaton.grammar)
104
+ nodes, symbol_kinds = propagate(graph, seeds.ids, settings)
105
+ automaton_impact = Impact::AutomatonImpact.new(automaton, nodes.keys)
106
+ coverage = load_coverage(automaton, automaton_impact.production_ids, settings)
107
+ reporter = Impact::Report.new(
108
+ mode: "potential", algorithm: automaton.algorithm, grammar: automaton.grammar, before: nil, after: automaton,
109
+ seeds: seeds.records, nodes: nodes, symbol_kinds: symbol_kinds, set_changes: {},
110
+ automaton: potential_automaton_document(automaton, automaton_impact), actions: [], coverage: coverage.to_h,
111
+ minimum: settings.fetch(:severity), warnings: nullable_warnings(seeds, coverage)
112
+ )
113
+ [reporter.to_h, conflict_identities(automaton), reporter.to_h(minimum: "info")]
114
+ end
115
+
116
+ # @rbs (Array[String], Hash[Symbol, untyped]) -> [Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?]]
117
+ def confirmed_impact(paths, settings)
118
+ before = load_analysis_automaton(paths.fetch(0), settings.fetch(:algorithm), explicit: algo_set?(settings))
119
+ after = load_analysis_automaton(paths.fetch(1), settings.fetch(:algorithm), explicit: algo_set?(settings))
120
+ diff = Diff.new(before, after).to_h
121
+ rules = diff.fetch(:rules) #: Hash[Symbol, Array[Hash[Symbol, Object?]]]
122
+ names = rules.values_at(:added, :removed, :changed).flatten.map do |record|
123
+ record.fetch(:id).to_s
124
+ end.uniq.sort
125
+ seed_names = names.select { |name| after.grammar.symbol(name) }
126
+ seeds = Impact::Seeds.new(after.grammar, seed_names, origin: "diff")
127
+ removed_seed_names = names.reject { |name| after.grammar.symbol(name) }
128
+ removed_seeds = Impact::Seeds.new(before.grammar, removed_seed_names, origin: "diff")
129
+ seed_records = (seeds.records + removed_seeds.records).sort_by { |record| record.fetch(:symbol).to_s }
130
+ symbol_changes = diff.fetch(:symbols) #: Hash[Symbol, Array[Hash[Symbol, Object?]]]
131
+ metadata_names = symbol_changes.values.flatten.map { |record| record.fetch(:id).to_s }.uniq.sort
132
+ graph = Impact::Graph.new(after.grammar)
133
+ nodes, symbol_kinds = propagate(graph, seeds.ids, settings)
134
+ set_changes, changed_kinds = compare_sets(before, after)
135
+ symbol_kinds = merge_symbol_kinds(symbol_kinds, changed_kinds, after.grammar)
136
+ precedence_kinds = precedence_symbol_kinds(symbol_changes, after.grammar)
137
+ symbol_kinds = merge_symbol_kinds(symbol_kinds, precedence_kinds, after.grammar)
138
+ automaton_impact = Impact::AutomatonImpact.new(after, nodes.keys)
139
+ actions = Impact::ActionImpact.new(before.grammar, after.grammar, affected_names: names).to_a
140
+ coverage = load_coverage(after, automaton_impact.production_ids, settings)
141
+ reporter = Impact::Report.new(
142
+ mode: "diff", algorithm: after.algorithm, grammar: after.grammar, before: before, after: after,
143
+ seeds: seed_records, nodes: nodes, symbol_kinds: symbol_kinds, set_changes: set_changes,
144
+ metadata_names: metadata_names,
145
+ automaton: confirmed_automaton_document(before, after, diff, automaton_impact), actions: actions,
146
+ coverage: coverage.to_h, minimum: settings.fetch(:severity), warnings: coverage.warnings
147
+ )
148
+ [reporter.to_h, conflict_identities(after), reporter.to_h(minimum: "info")]
149
+ end
150
+
151
+ # @rbs (Impact::Graph, Array[Integer], Hash[Symbol, untyped]) ->
152
+ # [Hash[Integer, Impact::Node], Hash[Integer, Array[String]]]
153
+ def propagate(graph, seed_ids, settings)
154
+ kinds = settings.fetch(:kinds) == [:all] ? %i[reference first follow] : settings.fetch(:kinds)
155
+ nodes = {} #: Hash[Integer, Impact::Node]
156
+ symbol_kinds = Hash.new { |hash, key| hash[key] = [] } #: Hash[Integer, Array[String]]
157
+ kinds.each do |kind|
158
+ current = Impact::Propagation.new(graph).propagate(seed_ids, kind, max_depth: settings[:depth])
159
+ current.each do |id, node|
160
+ best = nodes[id]
161
+ nodes[id] = node if best.nil? || node.distance < best.distance
162
+ symbol_kinds[id] << kind.to_s unless node.distance.zero? && node.witness.empty?
163
+ end
164
+ end
165
+ [nodes.sort.to_h, symbol_kinds.transform_values { |value| value.uniq.sort }]
166
+ end
167
+
168
+ # @rbs (IR::Automaton, Array[Integer], Hash[Symbol, untyped]) -> Impact::CoverageImpact
169
+ def load_coverage(automaton, production_ids, settings)
170
+ reports = settings.fetch(:coverage).map { |path| [path, Coverage::Report.load_file(path)] }
171
+ Impact::CoverageImpact.new(automaton, production_ids, reports)
172
+ end
173
+
174
+ # @rbs (Impact::Seeds, Impact::CoverageImpact) -> Array[String]
175
+ def nullable_warnings(seeds, coverage)
176
+ warnings = coverage.warnings.dup
177
+ seeds.records.each do |seed|
178
+ next unless seed.fetch(:nullable_boundary)
179
+
180
+ warnings << "#{seed.fetch(:symbol)}: nullable boundary may make potential propagation " \
181
+ "incomplete; compare two versions"
182
+ end
183
+ warnings
184
+ end
185
+
186
+ # @rbs (IR::Automaton, Impact::AutomatonImpact) -> Hash[Symbol, Object?]
187
+ def potential_automaton_document(automaton, impact)
188
+ added = conflict_identities(automaton).map { |id| { id: id, value: nil } } #: Array[Hash[Symbol, Object?]]
189
+ removed = [] #: Array[Hash[Symbol, Object?]]
190
+ changed = [] #: Array[Hash[Symbol, Object?]]
191
+ { states: { before: nil, after: automaton.states.length, delta: nil },
192
+ affected_states: impact.affected_states,
193
+ conflicts: { added: added, removed: removed, changed: changed },
194
+ unreachable: unreachable_state_ids(automaton),
195
+ unreachable_nonterminals: unreachable_nonterminal_names(automaton.grammar) }
196
+ end
197
+
198
+ # @rbs (IR::Automaton, IR::Automaton, Hash[Symbol, Object?], Impact::AutomatonImpact) -> Hash[Symbol, Object?]
199
+ def confirmed_automaton_document(before, after, diff, impact)
200
+ {
201
+ states: {
202
+ before: before.states.length, after: after.states.length, delta: after.states.length - before.states.length
203
+ },
204
+ affected_states: impact.affected_states, conflicts: diff.fetch(:conflicts),
205
+ unreachable: newly_unreachable_state_ids(before, after),
206
+ unreachable_nonterminals: newly_unreachable_nonterminal_names(before.grammar, after.grammar)
207
+ }
208
+ end
209
+
210
+ # @rbs (IR::Grammar) -> Array[String]
211
+ def unreachable_nonterminal_names(grammar)
212
+ grammar.warnings.filter_map do |warning|
213
+ next unless warning.fetch(:type).to_sym == :unreachable_nonterminal
214
+
215
+ warning.fetch(:symbol).to_s
216
+ end.uniq.sort
217
+ end
218
+
219
+ # @rbs (IR::Grammar, IR::Grammar) -> Array[String]
220
+ def newly_unreachable_nonterminal_names(before, after)
221
+ unreachable_nonterminal_names(after) - unreachable_nonterminal_names(before)
222
+ end
223
+
224
+ # @rbs (IR::Automaton) -> Array[Integer]
225
+ def unreachable_state_ids(automaton)
226
+ reachable = LALR::UnreachableStates.reachable_states(automaton.states, automaton.entry_states.values)
227
+ (0...automaton.states.length).to_a - reachable
228
+ end
229
+
230
+ # @rbs (IR::Automaton, IR::Automaton) -> Array[Integer]
231
+ def newly_unreachable_state_ids(before, after)
232
+ before_keys = unreachable_state_keys(before)
233
+ unreachable_state_ids(after).reject { |id| before_keys.include?(unreachable_state_key(after, id)) }
234
+ end
235
+
236
+ # @rbs (IR::Automaton) -> Array[String]
237
+ def unreachable_state_keys(automaton)
238
+ unreachable_state_ids(automaton).map { |id| unreachable_state_key(automaton, id) }
239
+ end
240
+
241
+ # @rbs (IR::Automaton, Integer) -> String
242
+ def unreachable_state_key(automaton, id)
243
+ state = automaton.states.fetch(id)
244
+ items = state.items.map do |item|
245
+ production_name = if item.production == LALR::Builder::AUGMENTED_PRODUCTION
246
+ "$accept"
247
+ else
248
+ production = automaton.grammar.productions.fetch(item.production)
249
+ production_shape(automaton.grammar, production.id)
250
+ end
251
+ [production_name, item.dot,
252
+ item.lookaheads.map { |lookahead| automaton.grammar.symbol_by_id(lookahead)&.name || lookahead.to_s }]
253
+ end.sort_by(&:inspect)
254
+ JSON.generate(items)
255
+ end
256
+
257
+ # @rbs (IR::Automaton, IR::Automaton) ->
258
+ # [Hash[String, Hash[Symbol, Object?]], Hash[String, Array[String]]]
259
+ def compare_sets(before, after)
260
+ before_sets = Analysis::Sets.new(before.grammar)
261
+ after_sets = Analysis::Sets.new(after.grammar)
262
+ names = (before.grammar.nonterminals.map(&:name) + after.grammar.nonterminals.map(&:name)).uniq.sort
263
+ changes = {} #: Hash[String, Hash[Symbol, Object?]]
264
+ kinds = {} #: Hash[String, Array[String]]
265
+ names.each do |name|
266
+ before_symbol = before.grammar.symbol(name)
267
+ after_symbol = after.grammar.symbol(name)
268
+ first = set_change(before_sets, after_sets, before_symbol, after_symbol, :first)
269
+ follow = set_change(before_sets, after_sets, before_symbol, after_symbol, :follow)
270
+ nullable = nullable_change(before_sets, after_sets, before_symbol, after_symbol)
271
+ next if set_change_empty?(first, follow, nullable)
272
+
273
+ changes[name] = { first: first, follow: follow, nullable: nullable }
274
+ kinds[name] = []
275
+ kinds[name] << "first" unless first[:added].empty? && first[:removed].empty?
276
+ kinds[name] << "follow" unless follow[:added].empty? && follow[:removed].empty?
277
+ kinds[name] << "nullable" if nullable
278
+ end
279
+ [changes, kinds]
280
+ end
281
+
282
+ # @rbs (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?, Symbol) ->
283
+ # Hash[Symbol, Array[String]]
284
+ def set_change(before, after, before_symbol, after_symbol, kind)
285
+ before_values = before_symbol&.nonterminal? ? before.public_send(kind, before_symbol) : [] #: Array[String]
286
+ after_values = after_symbol&.nonterminal? ? after.public_send(kind, after_symbol) : [] #: Array[String]
287
+ { added: after_values - before_values, removed: before_values - after_values }
288
+ end
289
+
290
+ # @rbs (Hash[Symbol, Array[String]], Hash[Symbol, Array[String]], Hash[Symbol, Object?]?) -> bool
291
+ def set_change_empty?(first, follow, nullable)
292
+ first_empty = first[:added].empty? && first[:removed].empty?
293
+ follow_empty = follow[:added].empty? && follow[:removed].empty?
294
+ first_empty && follow_empty && nullable.nil?
295
+ end
296
+
297
+ # @rbs (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?) -> Hash[Symbol, Object?]?
298
+ def nullable_change(before, after, before_symbol, after_symbol)
299
+ return nil unless before_symbol || after_symbol
300
+
301
+ old = before_symbol ? before.nullable?(before_symbol) : false
302
+ current = after_symbol ? after.nullable?(after_symbol) : false
303
+ return nil if old == current
304
+
305
+ { before: old, after: current }
306
+ end
307
+
308
+ # @rbs (Hash[Integer, Array[String]], Hash[String, Array[String]], IR::Grammar) -> Hash[Integer, Array[String]]
309
+ def merge_symbol_kinds(symbol_kinds, changed_kinds, grammar)
310
+ result = symbol_kinds.transform_values(&:dup)
311
+ changed_kinds.each do |name, kinds|
312
+ id = grammar.symbol(name)&.id
313
+ next unless id
314
+
315
+ result[id] = (result.fetch(id, []) + kinds).uniq.sort
316
+ end
317
+ result
318
+ end
319
+
320
+ # @rbs (Hash[Symbol, Array[Hash[Symbol, Object?]]], IR::Grammar) -> Hash[String, Array[String]]
321
+ def precedence_symbol_kinds(symbol_changes, grammar)
322
+ result = {} #: Hash[String, Array[String]]
323
+ symbol_changes.fetch(:changed).each do |record|
324
+ before = record.fetch(:before)
325
+ after = record.fetch(:after)
326
+ next unless before.is_a?(Hash) && after.is_a?(Hash)
327
+ next if before.fetch(:precedence) == after.fetch(:precedence)
328
+
329
+ name = record.fetch(:id).to_s
330
+ result[name] = ["precedence"] if grammar.symbol(name)
331
+ end
332
+ result
333
+ end
334
+
335
+ # @rbs (IR::Automaton) -> Array[String]
336
+ def conflict_identities(automaton)
337
+ automaton.states.flat_map do |state|
338
+ state.conflicts.map { |conflict| conflict_identity(automaton.grammar, conflict) }
339
+ end.uniq.sort
340
+ end
341
+
342
+ # @rbs (IR::Grammar, Hash[Symbol, Object?]) -> String
343
+ def conflict_identity(grammar, conflict)
344
+ type = conflict.fetch(:type).to_s.to_sym
345
+ symbol = conflict.fetch(:symbol).to_s
346
+ if type == :shift_reduce
347
+ reduce = conflict.fetch(:reduce) #: Integer
348
+ "shift_reduce:#{symbol}:#{production_shape(grammar, reduce)}"
349
+ else
350
+ reduction_ids = conflict.fetch(:reductions) #: Array[Integer]
351
+ reductions = reduction_ids.map { |id| production_shape(grammar, id) }.sort
352
+ "reduce_reduce:#{symbol}:#{reductions.join('|')}"
353
+ end
354
+ end
355
+
356
+ # @rbs (IR::Grammar, Integer) -> String
357
+ def production_shape(grammar, id)
358
+ production = grammar.productions.fetch(id)
359
+ lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
360
+ rhs = production.rhs.map { |symbol_id| grammar.symbol_by_id(symbol_id)&.name || symbol_id.to_s }
361
+ "#{lhs}->#{rhs.join(' ')}"
362
+ end
363
+
364
+ # @rbs (Hash[Symbol, Object?], Array[String], Hash[Symbol, untyped]) -> void
365
+ def apply_baseline(report, identities, settings)
366
+ path = settings[:baseline]
367
+ baseline = Impact::Baseline.new(path) if path
368
+ baseline.write(identities) if baseline && settings[:update_baseline]
369
+ return unless baseline
370
+
371
+ known = baseline.conflicts
372
+ conflicts = report.dig(:automaton, :conflicts, :added)
373
+ return unless conflicts
374
+
375
+ original_count = conflicts.length
376
+ conflicts.reject! { |entry| known.include?(entry.fetch(:id)) }
377
+ totals = report.fetch(:totals) #: Hash[Symbol, Integer]
378
+ totals[:critical] -= original_count - conflicts.length
379
+ end
380
+
381
+ # @rbs (Hash[Symbol, Object?], String) -> void
382
+ def write_impact_report(report, format)
383
+ return @stdout.puts(JSON.pretty_generate(report)) if format == "json"
384
+
385
+ symbols = report.fetch(:symbols) #: Array[untyped]
386
+ warnings = report.fetch(:warnings) #: Array[String]
387
+ @stdout.puts("impact mode=#{report.fetch(:mode)} symbols=#{symbols.length} " \
388
+ "affected_states=#{report.dig(:automaton, :affected_states).length}")
389
+ warnings.each { |warning| @stdout.puts("warning: #{warning}") }
390
+ end
391
+ end
392
+ # rubocop:enable Metrics/ModuleLength, Metrics/MethodLength, Metrics/AbcSize
393
+ end
@@ -1,22 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "tempfile"
4
3
  require_relative "../ir"
5
4
 
6
5
  module Ibex
7
- # @rbs!
8
- # type migrate_ir_options = {
9
- # paths: Array[String],
10
- # ?to: Integer,
11
- # ?output: String
12
- # }
13
-
14
- # CLI validation and structural comparison for versioned IR documents.
6
+ # CLI validation and structural comparison for current IR documents.
15
7
  module CLIIRTools
16
- # @rbs!
17
- # private def same_file_target?: (String left, String right) -> bool
18
- # private def atomic_write_ir: (String path, String source) -> void
19
-
20
8
  private
21
9
 
22
10
  # @rbs (Array[String] arguments) -> Integer
@@ -30,7 +18,7 @@ module Ibex
30
18
  else
31
19
  "automaton"
32
20
  end
33
- @stdout.puts("valid #{kind} IR v#{value.schema_version}")
21
+ @stdout.puts("valid current #{kind} IR")
34
22
  0
35
23
  end
36
24
 
@@ -49,50 +37,6 @@ module Ibex
49
37
  0
50
38
  end
51
39
 
52
- # @rbs (Array[String] arguments) -> Integer
53
- def run_migrate_ir_command(arguments)
54
- settings = migrate_ir_options(arguments)
55
- input = single_ir_path(settings.fetch(:paths), "migrate-ir")
56
- output = settings[:output]
57
- if output && same_file_target?(input, output)
58
- raise Ibex::Error, "(cli):1:1: migrate-ir input and output paths must be distinct"
59
- end
60
-
61
- value = IR::Validator.validate(File.read(input))
62
- target = settings[:to] || raise(Ibex::Error, "(cli):1:1: migrate-ir requires --to=VERSION")
63
- if value.is_a?(IR::Lexer)
64
- raise Ibex::Error, "(cli):1:1: lexer IR is already at its only supported schema version" unless
65
- target == value.schema_version
66
-
67
- source = IR::Serialize.dump(value)
68
- output ? atomic_write_ir(output, source) : @stdout.write(source)
69
- return 0
70
- end
71
- unless IR::SUPPORTED_SCHEMA_VERSIONS.include?(target)
72
- raise Ibex::Error, "(cli):1:1: unsupported migration target schema_version #{target}"
73
- end
74
- if target < value.schema_version
75
- raise Ibex::Error,
76
- "(cli):1:1: downgrading schema_version #{value.schema_version} to #{target} is not supported"
77
- end
78
-
79
- source = IR::Serialize.dump(IR::Migration.to_version(value, to: target))
80
- output ? atomic_write_ir(output, source) : @stdout.write(source)
81
- 0
82
- end
83
-
84
- # @rbs (Array[String] arguments) -> migrate_ir_options
85
- def migrate_ir_options(arguments)
86
- settings = { paths: [] } #: migrate_ir_options
87
- parser = OptionParser.new do |options|
88
- options.banner = "Usage: ibex migrate-ir INPUT --to=VERSION [-o FILE]"
89
- options.on("--to=VERSION", Integer, "target schema version") { |value| settings[:to] = value }
90
- options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
91
- end
92
- settings[:paths] = parser.parse(arguments)
93
- settings
94
- end
95
-
96
40
  # @rbs (Array[String] arguments, String command) -> String
97
41
  def single_ir_path(arguments, command)
98
42
  return arguments.first if arguments.length == 1
@@ -101,7 +45,7 @@ module Ibex
101
45
  end
102
46
 
103
47
  # @rbs (IR::Grammar | IR::Automaton | IR::Lexer before,
104
- # IR::Grammar | IR::Automaton | IR::Lexer after) -> Hash[Symbol, untyped]
48
+ # IR::Grammar | IR::Automaton | IR::Lexer after) -> Hash[Symbol, Object?]
105
49
  def compare_ir(before, after)
106
50
  return compare_grammars(before, after) if before.is_a?(IR::Grammar) && after.is_a?(IR::Grammar)
107
51
  if before.is_a?(IR::Automaton) && after.is_a?(IR::Automaton)
@@ -121,7 +65,7 @@ module Ibex
121
65
  raise Ibex::Error, "(cli):1:1: unsupported IR comparison"
122
66
  end
123
67
 
124
- # @rbs (IR::Lexer before, IR::Lexer after) -> Hash[Symbol, untyped]
68
+ # @rbs (IR::Lexer before, IR::Lexer after) -> Hash[Symbol, Object?]
125
69
  def compare_lexers(before, after)
126
70
  before_rules = before.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
127
71
  after_rules = after.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
@@ -136,7 +80,7 @@ module Ibex
136
80
  }
137
81
  end
138
82
 
139
- # @rbs (IR::Grammar before, IR::Grammar after) -> Hash[Symbol, untyped]
83
+ # @rbs (IR::Grammar before, IR::Grammar after) -> Hash[Symbol, Object?]
140
84
  def compare_grammars(before, after)
141
85
  before_symbols = before.symbols.map(&:name)
142
86
  after_symbols = after.symbols.map(&:name)
@@ -5,23 +5,19 @@ module Ibex
5
5
  module CLIOutputs
6
6
  # @rbs!
7
7
  # private def register_artifact: (Symbol, String, String, ?mode: Integer?, ?status: bool) -> Artifact
8
-
9
- WARNING_MESSAGES = {
10
- undeclared_terminal: ->(warning) { "undeclared terminal #{warning[:symbol]}" },
11
- unused_terminal: ->(warning) { "unused terminal #{warning[:symbol]}" },
12
- unused_precedence: ->(warning) { "unused precedence #{warning[:symbol]}" },
13
- unreachable_terminal: ->(warning) { "declared terminal #{warning[:symbol]} is unreachable" },
14
- unreachable_nonterminal: ->(warning) { "unreachable nonterminal #{warning[:symbol]}" },
15
- duplicate_production: lambda do |warning|
16
- "duplicate production #{warning[:production]} (first defined as #{warning[:original]})"
17
- end,
18
- implicit_empty: ->(_warning) { "implicit empty alternative; write %empty to document intent" },
19
- empty_language: ->(warning) { "start symbol #{warning[:symbol]} derives no terminal sentence" },
20
- lexer_redos: lambda do |warning|
21
- subject = warning[:symbol] ? " for #{warning[:symbol]}" : ""
22
- "lexer pattern#{subject} may exhibit excessive backtracking"
23
- end
24
- }.freeze #: Hash[Symbol, ^(IR::grammar_warning) -> String]
8
+ # private def configuration_value: (String) -> Object?
9
+
10
+ WARNING_MESSAGE_IDS = {
11
+ undeclared_terminal: "warning.undeclared_terminal",
12
+ unused_terminal: "warning.unused_terminal",
13
+ unused_precedence: "warning.unused_precedence",
14
+ unreachable_terminal: "warning.unreachable_terminal",
15
+ unreachable_nonterminal: "warning.unreachable_nonterminal",
16
+ duplicate_production: "warning.duplicate_production",
17
+ implicit_empty: "warning.implicit_empty",
18
+ empty_language: "warning.empty_language",
19
+ lexer_redos: "warning.lexer_redos"
20
+ }.freeze #: Hash[Symbol, String]
25
21
 
26
22
  private
27
23
 
@@ -42,32 +38,45 @@ module Ibex
42
38
  categories = @options[:warnings]
43
39
  return if categories.nil? || categories.include?(:none) || grammar.warnings.empty?
44
40
 
45
- messages = grammar.warnings.map { |warning| format_grammar_warning(warning, input_path) }
46
41
  if categories.include?(:error)
47
- promoted = messages.map { |message| message.sub(": warning:", ": warning treated as error:") }
42
+ promoted = grammar.warnings.map do |warning|
43
+ format_grammar_warning(warning, input_path, promoted: true)
44
+ end
48
45
  raise Ibex::Error, promoted.join("\n")
49
46
  end
50
47
 
51
- messages.each { |message| @stderr.puts(message) }
48
+ grammar.warnings.each { |warning| @stderr.puts(format_grammar_warning(warning, input_path)) }
52
49
  end
53
50
 
54
- # @rbs (IR::grammar_warning warning, String input_path) -> String
55
- def format_grammar_warning(warning, input_path)
51
+ # @rbs (IR::grammar_warning warning, String input_path, ?promoted: bool) -> String
52
+ def format_grammar_warning(warning, input_path, promoted: false)
56
53
  location = warning[:loc]
57
54
  rendered = if location
58
55
  "#{location[:file] || input_path}:#{location[:line] || 1}:#{location[:column] || 1}"
59
56
  else
60
57
  "#{input_path}:1:1"
61
58
  end
62
- formatter = WARNING_MESSAGES.fetch(warning[:type]) { ->(item) { item[:type].to_s.tr("_", " ") } }
63
- "#{rendered}: warning: #{formatter.call(warning)}"
59
+ label_id = promoted ? "label.warning_as_error" : "label.warning"
60
+ label = Messages.translate(label_id, language: @language)
61
+ "#{rendered}: #{label}: #{grammar_warning_message(warning)}"
62
+ end
63
+
64
+ # @rbs (IR::grammar_warning warning) -> String
65
+ def grammar_warning_message(warning)
66
+ id = WARNING_MESSAGE_IDS.fetch(warning[:type])
67
+ id = "warning.lexer_redos_symbol" if warning[:type] == :lexer_redos && warning[:symbol]
68
+ values = warning.to_h.transform_values(&:to_s) #: Hash[Symbol, String]
69
+ Messages.translate(id, language: @language, **values)
64
70
  end
65
71
 
66
72
  # @rbs (IR::Automaton automaton, String input_path) -> void
67
73
  def report_conflicts(automaton, input_path)
68
74
  messages = conflict_messages(automaton.conflict_summary, input_path)
69
75
  if @options[:warnings]&.include?(:error) && messages.any?
70
- raise Ibex::Error, messages.map { |message| "#{message}; conflict treated as error" }.join("\n")
76
+ promoted = messages.map do |message|
77
+ Messages.translate("conflict.treated_as_error", language: @language, detail: message)
78
+ end
79
+ raise Ibex::Error, promoted.join("\n")
71
80
  end
72
81
 
73
82
  messages.each { |message| @stderr.puts(message) }
@@ -77,29 +86,44 @@ module Ibex
77
86
  def conflict_messages(summary, input_path)
78
87
  messages = [] #: Array[String]
79
88
  unless summary[:expectation_met]
80
- messages << "#{input_path}:1:1: #{summary[:sr]} shift/reduce conflicts; expected #{summary[:expected_sr]}"
89
+ detail = Messages.translate(
90
+ "conflict.shift_reduce_expected",
91
+ language: @language,
92
+ actual: summary[:sr],
93
+ expected: summary[:expected_sr]
94
+ )
95
+ messages << "#{input_path}:1:1: #{detail}"
81
96
  end
82
97
  if summary.key?(:rr_expectation_met)
83
98
  unless summary[:rr_expectation_met]
84
- messages << "#{input_path}:1:1: #{summary[:rr]} reduce/reduce conflicts; expected #{summary[:expected_rr]}"
99
+ detail = Messages.translate(
100
+ "conflict.reduce_reduce_expected",
101
+ language: @language,
102
+ actual: summary[:rr],
103
+ expected: summary[:expected_rr]
104
+ )
105
+ messages << "#{input_path}:1:1: #{detail}"
85
106
  end
86
107
  elsif summary[:rr].positive?
87
- messages << "#{input_path}:1:1: #{summary[:rr]} reduce/reduce conflicts"
108
+ detail = Messages.translate("conflict.reduce_reduce", language: @language, actual: summary[:rr])
109
+ messages << "#{input_path}:1:1: #{detail}"
88
110
  end
89
111
  messages
90
112
  end
91
113
 
92
114
  # @rbs (IR::Automaton automaton, String input_path) -> void
115
+ # rubocop:disable Metrics/AbcSize -- the bounded comparison reports each conflict class independently.
93
116
  def suggest_ielr(automaton, input_path)
94
117
  return unless @options[:suggest_ielr]
95
118
  return unless automaton.algorithm == "lalr1"
96
- return unless [nil, :lalr].include?(@options[:algorithm])
119
+ return unless configuration_value("parser.algorithm") == :lalr
97
120
 
98
121
  summary = automaton.conflict_summary
99
122
  return if summary[:expectation_met] && summary.fetch(:rr_expectation_met, summary[:rr].zero?)
100
123
 
101
124
  ielr = LALR::Builder.new(
102
- automaton.grammar, algorithm: :ielr, entry_isolation: @options[:entry_isolation] == true
125
+ Configuration::AnalysisGrammar.for_algorithm(automaton.grammar, :ielr),
126
+ algorithm: :ielr, entry_isolation: configuration_value("parser.entries") == :isolated
103
127
  ).build
104
128
  removed_sr = [summary[:sr] - ielr.conflict_summary[:sr], 0].max
105
129
  removed_rr = [summary[:rr] - ielr.conflict_summary[:rr], 0].max
@@ -108,13 +132,16 @@ module Ibex
108
132
  avoided << conflict_count(removed_rr, "reduce/reduce") if removed_rr.positive?
109
133
  return if avoided.empty?
110
134
 
111
- @stderr.puts("#{input_path}:1:1: note: --algorithm=ielr avoids #{avoided.join(' and ')}; " \
112
- "consider --algorithm=ielr")
135
+ conjunction = @language == "ja" ? "、" : " and "
136
+ detail = Messages.translate("note.ielr", language: @language, avoided: avoided.join(conjunction))
137
+ @stderr.puts("#{input_path}:1:1: #{detail}")
113
138
  end
139
+ # rubocop:enable Metrics/AbcSize
114
140
 
115
141
  # @rbs (Integer count, String kind) -> String
116
142
  def conflict_count(count, kind)
117
- "#{count} #{kind} conflict#{'s' unless count == 1}"
143
+ plural = @language == "en" && count != 1 ? "s" : ""
144
+ Messages.translate("conflict.count", language: @language, count: count, kind: kind, plural: plural)
118
145
  end
119
146
 
120
147
  # @rbs (IR::Automaton automaton, String input_path) -> void