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,145 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "digest"
5
+ require "fileutils"
6
+ require "json"
7
+
8
+ module Ibex
9
+ # @rbs!
10
+ # type fuzz_external_description = {
11
+ # command: String,
12
+ # target_runtime: String,
13
+ # timeout_seconds: Integer,
14
+ # max_output_bytes: Integer,
15
+ # host_ruby_engine: String,
16
+ # host_ruby_version: String,
17
+ # host_platform: String
18
+ # }
19
+ # type fuzz_external_target = [
20
+ # ^(Array[String]) -> Symbol,
21
+ # fuzz_external_description
22
+ # ]
23
+
24
+ # Automatic bounded minimization and persistence for fuzz differences.
25
+ module CLIFuzzRegressions
26
+ # @rbs!
27
+ # private def atomic_write_ir: (String path, String source) -> void
28
+
29
+ private
30
+
31
+ # @rbs (Fuzz fuzzer, Fuzz::Mismatch mismatch, String grammar_path,
32
+ # fuzz_external_target? external) -> Integer
33
+ def write_minimized_fuzz_mismatch(fuzzer, mismatch, grammar_path, external)
34
+ details = minimized_fuzz_mismatch(fuzzer, mismatch, grammar_path, external)
35
+ report = {
36
+ ibex_report: "fuzz", schema_version: 1, result: "difference", mismatch: details
37
+ } #: Hash[Symbol, untyped]
38
+ report[:external] = external[1] if external
39
+ write_fuzz_report(report)
40
+ 1
41
+ end
42
+
43
+ # @rbs (Hash[Symbol, untyped] report) -> void
44
+ def write_fuzz_report(report)
45
+ if @options.fetch(:fuzz_format, "json") == "json"
46
+ @stdout.puts JSON.pretty_generate(report)
47
+ return
48
+ end
49
+
50
+ @stdout.puts("result=#{report.fetch(:result)}")
51
+ case report.fetch(:result)
52
+ when "no_difference_within_bounds"
53
+ @stdout.puts("seed=#{report.fetch(:seed)} bounds=#{report.fetch(:bounds).inspect}")
54
+ @stdout.puts("no difference found within the declared bounds; this is not a proof of equivalence")
55
+ when "difference"
56
+ mismatch = report.fetch(:mismatch) #: Hash[Symbol, untyped]
57
+ @stdout.puts("kind=#{mismatch.fetch(:kind)} tokens=#{mismatch.fetch(:tokens).inspect}")
58
+ @stdout.puts("minimized=#{mismatch.fetch(:minimized_tokens).inspect} " \
59
+ "reduction_complete=#{mismatch.dig(:reduction, :complete)}")
60
+ @stdout.puts("regression=#{mismatch.dig(:regression, :path)}") if mismatch[:regression]
61
+ when "budget_exhausted"
62
+ @stdout.puts("search incomplete: #{report.dig(:budget, :message)}")
63
+ end
64
+ external = report[:external] #: fuzz_external_description?
65
+ return unless external
66
+
67
+ @stdout.puts("target_runtime=#{external.fetch(:target_runtime)}")
68
+ @stdout.puts("target_command=#{external.fetch(:command)}")
69
+ end
70
+
71
+ # @rbs (Fuzz::BudgetExceeded error,
72
+ # fuzz_external_target? external, phase: String) -> Integer
73
+ def write_fuzz_budget_report(error, external, phase:)
74
+ report = {
75
+ ibex_report: "fuzz", schema_version: 1, result: "budget_exhausted",
76
+ budget: error.details.merge(phase: phase)
77
+ } #: Hash[Symbol, untyped]
78
+ report[:external] = external[1] if external
79
+ write_fuzz_report(report)
80
+ 2
81
+ end
82
+
83
+ # @rbs (Fuzz fuzzer, Fuzz::Mismatch mismatch, String grammar_path,
84
+ # fuzz_external_target? external) -> Hash[Symbol, untyped]
85
+ def minimized_fuzz_mismatch(fuzzer, mismatch, grammar_path, external)
86
+ result = fuzzer.minimize(
87
+ mismatch, max_trials: @options.fetch(:fuzz_max_reduction_trials, 1_000)
88
+ )
89
+ details = mismatch.details.dup #: Hash[Symbol, untyped]
90
+ details[:minimized_tokens] = result.items
91
+ details[:reduction] = {
92
+ original_size: result.original_size, minimized_size: result.items.length,
93
+ trials: result.trials, complete: result.complete
94
+ }
95
+ regression = persist_fuzz_regression(
96
+ mismatch.details, result, grammar_path, external&.[](1)
97
+ )
98
+ details[:regression] = regression if regression
99
+ details
100
+ end
101
+
102
+ # @rbs (fuzz_mismatch mismatch, DeltaReducer::Result result, String grammar_path,
103
+ # fuzz_external_description? external) -> Hash[Symbol, String]?
104
+ def persist_fuzz_regression(mismatch, result, grammar_path, external)
105
+ return if @options.fetch(:fuzz_save_regression, true) == false
106
+
107
+ directory = @options.fetch(:fuzz_regression_dir, "test/fuzz/regressions")
108
+ FileUtils.mkdir_p(directory)
109
+ document = fuzz_regression_document(mismatch, result, grammar_path, external)
110
+ source = "#{JSON.pretty_generate(document)}\n"
111
+ digest = Digest::SHA256.hexdigest(source)
112
+ seed = @options.fetch(:fuzz_seed, 0)
113
+ path = File.join(directory, "fuzz-seed-#{seed}-#{digest.slice(0, 16)}.json")
114
+ validate_fuzz_regression_target!(path)
115
+ atomic_write_ir(path, source)
116
+ { path: path, sha256: digest }
117
+ end
118
+
119
+ # @rbs (String path) -> void
120
+ def validate_fuzz_regression_target!(path)
121
+ return unless File.symlink?(path) || (File.exist?(path) && File.stat(path).nlink > 1)
122
+
123
+ raise Ibex::Error, "(fuzz):1:1: regression output refuses symlinks and files with multiple hard links"
124
+ end
125
+
126
+ # @rbs (fuzz_mismatch mismatch, DeltaReducer::Result result, String grammar_path,
127
+ # fuzz_external_description? external) -> Hash[Symbol, Object?]
128
+ def fuzz_regression_document(mismatch, result, grammar_path, external)
129
+ document = {
130
+ ibex_report: "fuzz-regression", schema_version: 1, grammar: grammar_path,
131
+ seed: @options.fetch(:fuzz_seed, 0), kind: mismatch[:kind],
132
+ sentence: mismatch[:sentence],
133
+ algorithms: mismatch[:outcomes].keys.reject { |name| name == :external }.map(&:to_s),
134
+ original_tokens: mismatch[:tokens], minimized_tokens: result.items,
135
+ bounds: mismatch[:bounds],
136
+ reduction: {
137
+ original_size: result.original_size, minimized_size: result.items.length,
138
+ trials: result.trials, complete: result.complete
139
+ }
140
+ } #: Hash[Symbol, Object?]
141
+ document[:external] = external if external
142
+ document
143
+ end
144
+ end
145
+ end
@@ -12,6 +12,8 @@ module Ibex
12
12
  # private def report_status: (String) -> void
13
13
  # private def verify_file: (String, String, String) -> void
14
14
  # private def default_output_path: (String, String) -> String
15
+ # private def configuration_value: (String) -> Object?
16
+ # private def effective_configuration: () -> Configuration::Resolver
15
17
 
16
18
  private
17
19
 
@@ -21,6 +23,7 @@ module Ibex
21
23
  @generation_statuses = [] #: Array[String]
22
24
  @generation_sources = [] #: Array[String]
23
25
  @generation_inputs = [] #: Array[GenerationInput]
26
+ @generation_automaton = nil
24
27
  end
25
28
 
26
29
  # @rbs (Symbol kind, String path, String content, ?mode: Integer?, ?status: bool) -> Artifact
@@ -120,19 +123,67 @@ module Ibex
120
123
  default_output_path(parser.path, ".ibex.json")
121
124
  end
122
125
 
123
- # @rbs () -> Hash[String, untyped]
126
+ # @rbs () -> Hash[String, Object?]
124
127
  def generation_manifest_options
125
128
  keys = %i[
126
129
  action_source algorithm counterexample_max_configurations counterexample_max_tokens debug
127
130
  embedded emit entry_isolation executable frozen line_convert line_convert_all messages mode omit_actions rbs
128
131
  superclass table
129
132
  ]
130
- options = {} #: Hash[String, untyped]
133
+ options = {} #: Hash[String, Object?]
131
134
  keys.each do |key|
132
135
  value = @options[key]
133
136
  options[key.to_s] = value unless value.nil?
134
137
  end
138
+ options["cst_trivia"] = effective_cst_trivia.to_s
139
+ append_ir_manifest_options(options)
135
140
  options
136
141
  end
142
+
143
+ # @rbs (Hash[String, Object?] options) -> void
144
+ def append_ir_manifest_options(options)
145
+ automaton = @generation_automaton
146
+ return unless automaton
147
+
148
+ grammar = automaton.grammar
149
+ contract = grammar.parser_contract
150
+ options["grammar_ir"] = {
151
+ "schema_version" => grammar.schema_version,
152
+ "digest" => automaton.grammar_digest,
153
+ "parser_contract" => stringify_contract(contract)
154
+ }
155
+ options["automaton_ir"] = {
156
+ "schema_version" => automaton.schema_version,
157
+ "algorithm" => automaton.algorithm,
158
+ "entry_construction" => automaton.entry_construction,
159
+ "construction_authority" => construction_authority
160
+ }
161
+ options["effective_configuration"] = manifest_effective_configuration
162
+ end
163
+
164
+ # @rbs () -> String
165
+ def construction_authority
166
+ @options[:from] == "automaton-ir" ? "embedded_automaton" : "grammar_contract"
167
+ end
168
+
169
+ # @rbs () -> Array[Hash[String, Object?]]
170
+ def manifest_effective_configuration
171
+ values = effective_configuration.to_h.fetch("configuration")
172
+ return values unless @options[:from] == "automaton-ir"
173
+
174
+ values.reject { |entry| %w[parser.algorithm parser.entries].include?(entry.fetch("key")) }
175
+ end
176
+
177
+ # @rbs (IR::ParserContract contract) -> Hash[String, Object?]
178
+ def stringify_contract(contract)
179
+ contract.to_h.to_h do |name, entry|
180
+ [name.to_s, entry.to_h { |key, value| [key.to_s, value] }]
181
+ end
182
+ end
183
+
184
+ # @rbs () -> Symbol
185
+ def effective_cst_trivia
186
+ configuration_value("cst.trivia") #: Symbol
187
+ end
137
188
  end
138
189
  end
@@ -16,7 +16,7 @@ module Ibex
16
16
  end
17
17
  end
18
18
 
19
- # @rbs (IR::Automaton automaton) -> Hash[Integer, untyped]
19
+ # @rbs (IR::Automaton automaton) -> Hash[Integer, String | { id: String, message: String }]
20
20
  def configured_error_messages(automaton)
21
21
  path = @options[:messages]
22
22
  return {} unless path
@@ -7,26 +7,19 @@ module Ibex
7
7
  # `%test` command parsing and human-readable result reporting.
8
8
  module CLIGrammarTests
9
9
  # @rbs!
10
- # type grammar_test_settings = {
11
- # mode: Symbol,
12
- # algorithm: Symbol,
13
- # entry_isolation: bool,
14
- # timeout: Integer,
15
- # coverage: Integer?,
16
- # ?help: bool
17
- # }
10
+ # type grammar_test_settings = Hash[Symbol, untyped]
18
11
  # private def print_help: (OptionParser) -> Integer
19
12
  # private def resolve_grammar_path: (String) -> Frontend::Resolution
20
13
  # private def handle_grammar_warnings: (IR::Grammar, String) -> void
21
14
  # private def build_automaton: (IR::Grammar, String) -> IR::Automaton
15
+ # private def configuration_value: (String) -> untyped
16
+ # private def mark_configuration_option: (Symbol) -> void
22
17
 
23
18
  private
24
19
 
25
20
  # @rbs (Array[String] arguments) -> Integer
26
21
  def run_grammar_tests_command(arguments)
27
- settings = {
28
- mode: :default, algorithm: :lalr, entry_isolation: false, timeout: GrammarTests::DEFAULT_TIMEOUT, coverage: nil
29
- } #: grammar_test_settings
22
+ settings = default_grammar_test_settings
30
23
  options = grammar_test_option_parser(settings)
31
24
  remaining = options.parse(arguments)
32
25
  return print_help(options) if settings[:help]
@@ -39,7 +32,7 @@ module Ibex
39
32
  @options[:algorithm] = settings[:algorithm]
40
33
  @options[:entry_isolation] = settings[:entry_isolation]
41
34
  resolution = resolve_grammar_path(path)
42
- grammar = Normalizer.new(resolution, mode: settings[:mode]).normalize
35
+ grammar = normalize_analysis_grammar(resolution)
43
36
  handle_grammar_warnings(grammar, path)
44
37
  automaton = build_automaton(grammar, path)
45
38
  runner = GrammarTests::Runner.new(automaton, timeout: settings[:timeout])
@@ -51,16 +44,31 @@ module Ibex
51
44
  render_grammar_test_coverage(runner.production_coverage(results), coverage, failures)
52
45
  end
53
46
 
54
- # @rbs (grammar_test_settings settings) -> OptionParser
47
+ # @rbs () -> Hash[Symbol, untyped]
48
+ def default_grammar_test_settings
49
+ {
50
+ mode: Configuration::Registry.fetch("grammar.mode").default,
51
+ algorithm: Configuration::Registry.fetch("parser.algorithm").default,
52
+ entry_isolation: Configuration::Registry.fetch("parser.entries").default == :isolated,
53
+ timeout: GrammarTests::DEFAULT_TIMEOUT, coverage: nil
54
+ }
55
+ end
56
+
57
+ # @rbs (Hash[Symbol, untyped] settings) -> OptionParser
55
58
  def grammar_test_option_parser(settings)
56
59
  OptionParser.new do |options|
57
60
  options.banner = "Usage: ibex test [options] grammarfile"
58
- options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
61
+ options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
62
+ settings[:mode] = value.to_sym
63
+ mark_configuration_option(:mode)
64
+ end
59
65
  options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
60
66
  settings[:algorithm] = value.to_sym
67
+ mark_configuration_option(:algorithm)
61
68
  end
62
69
  options.on("--entry-isolation", "build independent state sets for each start symbol") do
63
70
  settings[:entry_isolation] = true
71
+ mark_configuration_option(:entry_isolation)
64
72
  end
65
73
  options.on("--timeout=SECONDS", Integer, "whole-suite timeout (default: 10)") do |value|
66
74
  raise OptionParser::InvalidArgument, "timeout must be positive" unless value.positive?
@@ -83,6 +91,16 @@ module Ibex
83
91
  raise OptionParser::InvalidArgument, "test accepts exactly one grammarfile"
84
92
  end
85
93
 
94
+ # @rbs (Frontend::Resolution resolution) -> IR::Grammar
95
+ def normalize_analysis_grammar(resolution)
96
+ grammar = Normalizer.new(resolution, mode: configuration_value("grammar.mode")).normalize
97
+ activate_analysis_grammar(grammar)
98
+ end
99
+
100
+ # @rbs!
101
+ # private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
102
+ # ?explicit_keys: Array[Symbol]) -> IR::Grammar
103
+
86
104
  # @rbs (Array[GrammarTests::Result] results) -> Integer
87
105
  def render_grammar_test_results(results)
88
106
  failures = 0
@@ -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