ibex 0.1.0 → 0.2.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 (384) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +247 -104
  3. data/docs/architecture.md +322 -28
  4. data/docs/cst-migration.md +107 -0
  5. data/docs/cst.md +213 -0
  6. data/docs/development.md +129 -0
  7. data/docs/editor-setup.md +25 -0
  8. data/docs/error-ux.md +60 -0
  9. data/docs/grammar-reference.md +458 -18
  10. data/docs/lexer-migration.md +51 -0
  11. data/docs/racc-migration.md +34 -6
  12. data/docs/release-readiness.md +163 -0
  13. data/docs/stability.md +124 -0
  14. data/examples/README.md +60 -0
  15. data/examples/calculator.y +44 -0
  16. data/examples/csv.y +32 -0
  17. data/examples/ini.y +70 -0
  18. data/examples/json.y +48 -0
  19. data/examples/tiny_language.y +75 -0
  20. data/lib/ibex/analysis/sets.rb +5 -4
  21. data/lib/ibex/artifact_set.rb +63 -0
  22. data/lib/ibex/cli/ambiguity.rb +66 -0
  23. data/lib/ibex/cli/counterexample_options.rb +6 -2
  24. data/lib/ibex/cli/coverage.rb +173 -0
  25. data/lib/ibex/cli/debug.rb +106 -0
  26. data/lib/ibex/cli/diagnostics.rb +116 -0
  27. data/lib/ibex/cli/documentation.rb +67 -0
  28. data/lib/ibex/cli/error_messages.rb +177 -0
  29. data/lib/ibex/cli/explain.rb +76 -0
  30. data/lib/ibex/cli/formatting.rb +386 -0
  31. data/lib/ibex/cli/generation_artifacts.rb +138 -0
  32. data/lib/ibex/cli/generation_error_messages.rb +39 -0
  33. data/lib/ibex/cli/grammar_tests.rb +122 -0
  34. data/lib/ibex/cli/ir_tools.rb +184 -0
  35. data/lib/ibex/cli/lsp.rb +33 -0
  36. data/lib/ibex/cli/outputs.rb +91 -8
  37. data/lib/ibex/cli/racc_migration.rb +87 -0
  38. data/lib/ibex/cli/samples.rb +92 -0
  39. data/lib/ibex/cli/watch.rb +103 -0
  40. data/lib/ibex/cli.rb +506 -37
  41. data/lib/ibex/codegen/action_locations.rb +103 -0
  42. data/lib/ibex/codegen/action_method_source.rb +210 -0
  43. data/lib/ibex/codegen/action_source.rb +118 -0
  44. data/lib/ibex/codegen/ambiguity.rb +173 -0
  45. data/lib/ibex/codegen/cst_metadata.rb +171 -0
  46. data/lib/ibex/codegen/documentation.rb +170 -0
  47. data/lib/ibex/codegen/explain.rb +312 -0
  48. data/lib/ibex/codegen/generated_action_abi.rb +288 -0
  49. data/lib/ibex/codegen/html.rb +94 -10
  50. data/lib/ibex/codegen/mermaid.rb +43 -0
  51. data/lib/ibex/codegen/railroad.rb +197 -0
  52. data/lib/ibex/codegen/railroad_documentation.rb +59 -0
  53. data/lib/ibex/codegen/rbs.rb +323 -2
  54. data/lib/ibex/codegen/report.rb +46 -6
  55. data/lib/ibex/codegen/ruby.rb +334 -59
  56. data/lib/ibex/codegen/ruby_actions.rb +143 -0
  57. data/lib/ibex/codegen/ruby_ast.rb +121 -0
  58. data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
  59. data/lib/ibex/codegen/ruby_lexer.rb +83 -0
  60. data/lib/ibex/codegen/ruby_syntax.rb +90 -0
  61. data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
  62. data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
  63. data/lib/ibex/codegen/symbol_labels.rb +1 -1
  64. data/lib/ibex/coverage/collector.rb +188 -0
  65. data/lib/ibex/coverage/event_stream.rb +97 -0
  66. data/lib/ibex/coverage/report.rb +259 -0
  67. data/lib/ibex/coverage/runtime_event_validator.rb +160 -0
  68. data/lib/ibex/coverage.rb +13 -0
  69. data/lib/ibex/error_messages/parser.rb +159 -0
  70. data/lib/ibex/error_messages/parser_v2.rb +198 -0
  71. data/lib/ibex/error_messages/renderer.rb +65 -0
  72. data/lib/ibex/error_messages/sentence_search.rb +196 -0
  73. data/lib/ibex/error_messages/update.rb +169 -0
  74. data/lib/ibex/error_messages.rb +165 -0
  75. data/lib/ibex/frontend/ast.rb +145 -6
  76. data/lib/ibex/frontend/bootstrap_parser.rb +47 -4
  77. data/lib/ibex/frontend/diagnostic.rb +81 -0
  78. data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
  79. data/lib/ibex/frontend/dsl.rb +66 -7
  80. data/lib/ibex/frontend/formatter.rb +407 -0
  81. data/lib/ibex/frontend/generated_parser.rb +614 -190
  82. data/lib/ibex/frontend/generated_parser_base.rb +164 -30
  83. data/lib/ibex/frontend/generated_parser_includes.rb +61 -0
  84. data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
  85. data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
  86. data/lib/ibex/frontend/generation.rb +33 -0
  87. data/lib/ibex/frontend/lexer.rb +162 -10
  88. data/lib/ibex/frontend/lexer_recovery.rb +84 -0
  89. data/lib/ibex/frontend/parser/declarations.rb +247 -11
  90. data/lib/ibex/frontend/parser/parameters.rb +82 -0
  91. data/lib/ibex/frontend/parser/rules.rb +43 -6
  92. data/lib/ibex/frontend/parser.rb +182 -4
  93. data/lib/ibex/frontend/regenerator.rb +26 -1
  94. data/lib/ibex/frontend/resolution.rb +69 -0
  95. data/lib/ibex/frontend/resolver.rb +217 -0
  96. data/lib/ibex/frontend/rule_documentation.rb +103 -0
  97. data/lib/ibex/frontend/source_cursor.rb +132 -8
  98. data/lib/ibex/frontend/source_document.rb +229 -0
  99. data/lib/ibex/frontend/source_loader.rb +150 -0
  100. data/lib/ibex/frontend/source_span.rb +81 -0
  101. data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
  102. data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
  103. data/lib/ibex/frontend/token_adapter/declaration_state.rb +216 -26
  104. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
  105. data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
  106. data/lib/ibex/frontend/token_adapter.rb +8 -3
  107. data/lib/ibex/frontend.rb +13 -2
  108. data/lib/ibex/generation_input.rb +57 -0
  109. data/lib/ibex/generation_manifest.rb +200 -0
  110. data/lib/ibex/generation_transaction.rb +261 -0
  111. data/lib/ibex/generation_transaction_recovery.rb +109 -0
  112. data/lib/ibex/generation_transaction_validation.rb +196 -0
  113. data/lib/ibex/grammar_tests.rb +206 -0
  114. data/lib/ibex/ir/automaton_ir.rb +38 -5
  115. data/lib/ibex/ir/grammar_ir.rb +137 -24
  116. data/lib/ibex/ir/lexer_ir.rb +76 -0
  117. data/lib/ibex/ir/migration.rb +120 -0
  118. data/lib/ibex/ir/serialize.rb +110 -19
  119. data/lib/ibex/ir/validator/automaton.rb +345 -0
  120. data/lib/ibex/ir/validator/base.rb +129 -0
  121. data/lib/ibex/ir/validator/grammar.rb +604 -0
  122. data/lib/ibex/ir/validator/lexer.rb +113 -0
  123. data/lib/ibex/ir/validator.rb +62 -0
  124. data/lib/ibex/ir.rb +57 -4
  125. data/lib/ibex/lalr/build_metrics.rb +23 -0
  126. data/lib/ibex/lalr/builder.rb +379 -52
  127. data/lib/ibex/lalr/conflict.rb +1 -0
  128. data/lib/ibex/lalr/conflict_search.rb +11 -5
  129. data/lib/ibex/lalr/counterexample.rb +20 -5
  130. data/lib/ibex/lalr/direct_lookaheads.rb +236 -0
  131. data/lib/ibex/lalr/ielr_partition.rb +152 -0
  132. data/lib/ibex/lalr/on_error_reductions.rb +74 -0
  133. data/lib/ibex/lalr.rb +7 -0
  134. data/lib/ibex/location.rb +129 -0
  135. data/lib/ibex/lsp/document_handlers.rb +66 -0
  136. data/lib/ibex/lsp/document_store.rb +264 -0
  137. data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
  138. data/lib/ibex/lsp/document_store_validation.rb +61 -0
  139. data/lib/ibex/lsp/initialization_handlers.rb +78 -0
  140. data/lib/ibex/lsp/navigation_handlers.rb +56 -0
  141. data/lib/ibex/lsp/position_codec.rb +109 -0
  142. data/lib/ibex/lsp/protocol_error.rb +33 -0
  143. data/lib/ibex/lsp/request_handlers.rb +45 -0
  144. data/lib/ibex/lsp/request_support.rb +87 -0
  145. data/lib/ibex/lsp/server.rb +146 -0
  146. data/lib/ibex/lsp/symbol_index.rb +241 -0
  147. data/lib/ibex/lsp/symbol_index_builder.rb +267 -0
  148. data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
  149. data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
  150. data/lib/ibex/lsp/symbol_occurrence.rb +17 -0
  151. data/lib/ibex/lsp/transport.rb +118 -0
  152. data/lib/ibex/lsp/workspace.rb +127 -0
  153. data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
  154. data/lib/ibex/lsp.rb +32 -0
  155. data/lib/ibex/normalize/declarations.rb +140 -7
  156. data/lib/ibex/normalize/diagnostics.rb +50 -5
  157. data/lib/ibex/normalize/expander.rb +59 -55
  158. data/lib/ibex/normalize/expression.rb +60 -39
  159. data/lib/ibex/normalize/inline_expansion.rb +414 -0
  160. data/lib/ibex/normalize/inline_validation.rb +174 -0
  161. data/lib/ibex/normalize/lexer.rb +131 -0
  162. data/lib/ibex/normalize/named_references.rb +60 -0
  163. data/lib/ibex/normalize/nodes.rb +46 -0
  164. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +69 -0
  165. data/lib/ibex/normalize/parameter_lowering.rb +126 -0
  166. data/lib/ibex/normalize/parameter_substitution.rb +125 -0
  167. data/lib/ibex/normalize/parameter_validation.rb +140 -0
  168. data/lib/ibex/normalize/parameters.rb +199 -0
  169. data/lib/ibex/normalize/recovery_declarations.rb +84 -0
  170. data/lib/ibex/normalize.rb +179 -14
  171. data/lib/ibex/racc_migration/checker.rb +122 -0
  172. data/lib/ibex/racc_migration/harness.rb +177 -0
  173. data/lib/ibex/racc_migration/report.rb +94 -0
  174. data/lib/ibex/racc_migration.rb +12 -0
  175. data/lib/ibex/rake_task.rb +116 -0
  176. data/lib/ibex/samples.rb +186 -0
  177. data/lib/ibex/table_simulation/result.rb +51 -0
  178. data/lib/ibex/table_simulation/simulator.rb +253 -0
  179. data/lib/ibex/table_simulation/step.rb +60 -0
  180. data/lib/ibex/table_simulation/text.rb +31 -0
  181. data/lib/ibex/table_simulation.rb +13 -0
  182. data/lib/ibex/tables.rb +9 -70
  183. data/lib/ibex/version.rb +1 -1
  184. data/lib/ibex/watch/runner.rb +172 -0
  185. data/lib/ibex/watch/source_snapshot.rb +93 -0
  186. data/lib/ibex/watch.rb +11 -0
  187. data/lib/ibex.rb +25 -1
  188. data/schema/automaton-ir-v1.schema.json +401 -0
  189. data/schema/automaton-ir-v2.schema.json +58 -0
  190. data/schema/benchmark-v1.schema.json +212 -0
  191. data/schema/benchmark-v2.schema.json +61 -0
  192. data/schema/cst-v1.json +170 -0
  193. data/schema/error-ux-v1.schema.json +258 -0
  194. data/schema/explain-v1.schema.json +433 -0
  195. data/schema/frontend-diagnostics-v1.schema.json +154 -0
  196. data/schema/generation-manifest-v1.schema.json +115 -0
  197. data/schema/grammar-ir-v1.schema.json +426 -0
  198. data/schema/grammar-ir-v2.schema.json +779 -0
  199. data/schema/lexer-ir-v1.schema.json +215 -0
  200. data/schema/migration-check-v1.schema.json +60 -0
  201. data/schema/performance-comparison-v1.schema.json +395 -0
  202. data/schema/public-performance-comparison-v1.schema.json +506 -0
  203. data/schema/public-performance-profile-v1.schema.json +360 -0
  204. data/schema/runtime-coverage-v1.schema.json +86 -0
  205. data/schema/runtime-event-v1.schema.json +308 -0
  206. data/schema/table-simulation-v1.schema.json +70 -0
  207. data/sig/ibex/artifact_set.rbs +37 -0
  208. data/sig/ibex/cli/ambiguity.rbs +22 -0
  209. data/sig/ibex/cli/counterexample_options.rbs +2 -0
  210. data/sig/ibex/cli/coverage.rbs +53 -0
  211. data/sig/ibex/cli/debug.rbs +28 -0
  212. data/sig/ibex/cli/diagnostics.rbs +38 -0
  213. data/sig/ibex/cli/documentation.rbs +25 -0
  214. data/sig/ibex/cli/error_messages.rbs +57 -0
  215. data/sig/ibex/cli/explain.rbs +25 -0
  216. data/sig/ibex/cli/formatting.rbs +103 -0
  217. data/sig/ibex/cli/generation_artifacts.rbs +50 -0
  218. data/sig/ibex/cli/generation_error_messages.rbs +19 -0
  219. data/sig/ibex/cli/grammar_tests.rbs +36 -0
  220. data/sig/ibex/cli/ir_tools.rbs +51 -0
  221. data/sig/ibex/cli/lsp.rbs +14 -0
  222. data/sig/ibex/cli/outputs.rbs +17 -0
  223. data/sig/ibex/cli/racc_migration.rbs +30 -0
  224. data/sig/ibex/cli/samples.rbs +30 -0
  225. data/sig/ibex/cli/watch.rbs +43 -0
  226. data/sig/ibex/cli.rbs +104 -5
  227. data/sig/ibex/codegen/action_locations.rbs +45 -0
  228. data/sig/ibex/codegen/action_method_source.rbs +65 -0
  229. data/sig/ibex/codegen/action_source.rbs +50 -0
  230. data/sig/ibex/codegen/ambiguity.rbs +60 -0
  231. data/sig/ibex/codegen/cst_metadata.rbs +59 -0
  232. data/sig/ibex/codegen/documentation.rbs +50 -0
  233. data/sig/ibex/codegen/explain.rbs +85 -0
  234. data/sig/ibex/codegen/generated_action_abi.rbs +101 -0
  235. data/sig/ibex/codegen/html.rbs +18 -2
  236. data/sig/ibex/codegen/mermaid.rbs +16 -0
  237. data/sig/ibex/codegen/railroad.rbs +82 -0
  238. data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
  239. data/sig/ibex/codegen/rbs.rbs +84 -4
  240. data/sig/ibex/codegen/report.rbs +8 -0
  241. data/sig/ibex/codegen/ruby.rbs +97 -23
  242. data/sig/ibex/codegen/ruby_actions.rbs +54 -0
  243. data/sig/ibex/codegen/ruby_ast.rbs +34 -0
  244. data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
  245. data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
  246. data/sig/ibex/codegen/ruby_syntax.rbs +25 -0
  247. data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
  248. data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
  249. data/sig/ibex/coverage/collector.rbs +76 -0
  250. data/sig/ibex/coverage/event_stream.rbs +42 -0
  251. data/sig/ibex/coverage/report.rbs +100 -0
  252. data/sig/ibex/coverage/runtime_event_validator.rbs +68 -0
  253. data/sig/ibex/coverage.rbs +7 -0
  254. data/sig/ibex/error_messages/parser.rbs +58 -0
  255. data/sig/ibex/error_messages/parser_v2.rbs +67 -0
  256. data/sig/ibex/error_messages/renderer.rbs +23 -0
  257. data/sig/ibex/error_messages/sentence_search.rbs +80 -0
  258. data/sig/ibex/error_messages/update.rbs +43 -0
  259. data/sig/ibex/error_messages.rbs +85 -0
  260. data/sig/ibex/frontend/ast.rbs +208 -19
  261. data/sig/ibex/frontend/bootstrap_parser.rbs +11 -0
  262. data/sig/ibex/frontend/diagnostic.rbs +53 -0
  263. data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
  264. data/sig/ibex/frontend/dsl.rbs +33 -4
  265. data/sig/ibex/frontend/formatter.rbs +135 -0
  266. data/sig/ibex/frontend/generated_parser.rbs +218 -68
  267. data/sig/ibex/frontend/generated_parser_base.rbs +61 -10
  268. data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
  269. data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
  270. data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
  271. data/sig/ibex/frontend/generation.rbs +6 -0
  272. data/sig/ibex/frontend/lexer.rbs +49 -2
  273. data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
  274. data/sig/ibex/frontend/parser/declarations.rbs +54 -0
  275. data/sig/ibex/frontend/parser/parameters.rbs +28 -0
  276. data/sig/ibex/frontend/parser/rules.rbs +3 -0
  277. data/sig/ibex/frontend/parser.rbs +66 -0
  278. data/sig/ibex/frontend/regenerator.rbs +11 -0
  279. data/sig/ibex/frontend/resolution.rbs +33 -0
  280. data/sig/ibex/frontend/resolver.rbs +91 -0
  281. data/sig/ibex/frontend/rule_documentation.rbs +42 -0
  282. data/sig/ibex/frontend/source_cursor.rbs +36 -3
  283. data/sig/ibex/frontend/source_document.rbs +119 -0
  284. data/sig/ibex/frontend/source_loader.rbs +66 -0
  285. data/sig/ibex/frontend/source_span.rbs +53 -0
  286. data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
  287. data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
  288. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +73 -5
  289. data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
  290. data/sig/ibex/frontend/token_adapter.rbs +5 -2
  291. data/sig/ibex/frontend.rbs +1 -1
  292. data/sig/ibex/generation_input.rbs +37 -0
  293. data/sig/ibex/generation_manifest.rbs +67 -0
  294. data/sig/ibex/generation_transaction.rbs +82 -0
  295. data/sig/ibex/generation_transaction_recovery.rbs +36 -0
  296. data/sig/ibex/generation_transaction_validation.rbs +65 -0
  297. data/sig/ibex/grammar_tests.rbs +93 -0
  298. data/sig/ibex/ir/automaton_ir.rbs +8 -2
  299. data/sig/ibex/ir/grammar_ir.rbs +75 -15
  300. data/sig/ibex/ir/lexer_ir.rbs +57 -0
  301. data/sig/ibex/ir/migration.rbs +34 -0
  302. data/sig/ibex/ir/serialize.rbs +23 -6
  303. data/sig/ibex/ir/validator/automaton.rbs +109 -0
  304. data/sig/ibex/ir/validator/base.rbs +65 -0
  305. data/sig/ibex/ir/validator/grammar.rbs +184 -0
  306. data/sig/ibex/ir/validator/lexer.rbs +37 -0
  307. data/sig/ibex/ir/validator.rbs +16 -0
  308. data/sig/ibex/ir.rbs +38 -4
  309. data/sig/ibex/lalr/build_metrics.rbs +20 -0
  310. data/sig/ibex/lalr/builder.rbs +95 -15
  311. data/sig/ibex/lalr/conflict_search.rbs +7 -3
  312. data/sig/ibex/lalr/counterexample.rbs +5 -2
  313. data/sig/ibex/lalr/direct_lookaheads.rbs +86 -0
  314. data/sig/ibex/lalr/ielr_partition.rbs +59 -0
  315. data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
  316. data/sig/ibex/lalr.rbs +6 -0
  317. data/sig/ibex/location.rbs +67 -0
  318. data/sig/ibex/lsp/document_handlers.rbs +24 -0
  319. data/sig/ibex/lsp/document_store.rbs +94 -0
  320. data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
  321. data/sig/ibex/lsp/document_store_validation.rbs +26 -0
  322. data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
  323. data/sig/ibex/lsp/navigation_handlers.rbs +30 -0
  324. data/sig/ibex/lsp/position_codec.rbs +40 -0
  325. data/sig/ibex/lsp/protocol_error.rbs +32 -0
  326. data/sig/ibex/lsp/request_handlers.rbs +26 -0
  327. data/sig/ibex/lsp/request_support.rbs +40 -0
  328. data/sig/ibex/lsp/server.rbs +55 -0
  329. data/sig/ibex/lsp/symbol_index.rbs +78 -0
  330. data/sig/ibex/lsp/symbol_index_builder.rbs +87 -0
  331. data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
  332. data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
  333. data/sig/ibex/lsp/symbol_occurrence.rbs +25 -0
  334. data/sig/ibex/lsp/transport.rbs +35 -0
  335. data/sig/ibex/lsp/workspace.rbs +41 -0
  336. data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
  337. data/sig/ibex/lsp.rbs +7 -0
  338. data/sig/ibex/normalize/declarations.rbs +31 -0
  339. data/sig/ibex/normalize/diagnostics.rbs +9 -0
  340. data/sig/ibex/normalize/expander.rbs +20 -14
  341. data/sig/ibex/normalize/expression.rbs +10 -10
  342. data/sig/ibex/normalize/inline_expansion.rbs +120 -0
  343. data/sig/ibex/normalize/inline_validation.rbs +40 -0
  344. data/sig/ibex/normalize/lexer.rbs +37 -0
  345. data/sig/ibex/normalize/named_references.rbs +20 -0
  346. data/sig/ibex/normalize/nodes.rbs +14 -0
  347. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +32 -0
  348. data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
  349. data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
  350. data/sig/ibex/normalize/parameter_validation.rbs +44 -0
  351. data/sig/ibex/normalize/parameters.rbs +46 -0
  352. data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
  353. data/sig/ibex/normalize.rbs +123 -18
  354. data/sig/ibex/racc_migration/checker.rbs +36 -0
  355. data/sig/ibex/racc_migration/harness.rbs +16 -0
  356. data/sig/ibex/racc_migration/report.rbs +53 -0
  357. data/sig/ibex/racc_migration.rbs +8 -0
  358. data/sig/ibex/rake_task.rbs +51 -0
  359. data/sig/ibex/samples.rbs +51 -0
  360. data/sig/ibex/table_simulation/result.rbs +32 -0
  361. data/sig/ibex/table_simulation/simulator.rbs +98 -0
  362. data/sig/ibex/table_simulation/step.rbs +40 -0
  363. data/sig/ibex/table_simulation/text.rbs +14 -0
  364. data/sig/ibex/table_simulation.rbs +7 -0
  365. data/sig/ibex/tables.rbs +0 -26
  366. data/sig/ibex/watch/runner.rbs +48 -0
  367. data/sig/ibex/watch/source_snapshot.rbs +42 -0
  368. data/sig/ibex/watch.rbs +7 -0
  369. data/sig/ibex.rbs +2 -0
  370. metadata +301 -16
  371. data/.rubocop.yml +0 -43
  372. data/CHANGELOG.md +0 -30
  373. data/Rakefile +0 -25
  374. data/Steepfile +0 -10
  375. data/docs/compat-notes.md +0 -37
  376. data/docs/lexer-coverage.md +0 -14
  377. data/docs/phase10-extensions.md +0 -27
  378. data/gemfiles/Gemfile +0 -7
  379. data/gemfiles/Gemfile.lock +0 -98
  380. data/lib/ibex/frontend/grammar.y +0 -156
  381. data/lib/ibex/runtime/parser.rb +0 -360
  382. data/lib/ibex/runtime.rb +0 -8
  383. data/sig/ibex/runtime/parser.rbs +0 -167
  384. data/sig/ibex/runtime.rbs +0 -6
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Codegen
6
+ # Builds the deterministic kind space embedded in CST-aware parser tables.
7
+ class CSTMetadata
8
+ # @rbs!
9
+ # type kind_map = Hash[String, Integer]
10
+ # type kinds = {
11
+ # names: Array[String],
12
+ # terminal_range: Array[Integer],
13
+ # nonterminal_range: Array[Integer],
14
+ # named: kind_map,
15
+ # named_nonterminals: Hash[Integer, Integer],
16
+ # trivia: kind_map,
17
+ # synthetic: kind_map
18
+ # }
19
+ # type field_slot = Integer | { index: Integer, extraction: Symbol }
20
+ # type slot = { node_kind: Integer, node_name: String, fields: Hash[String, field_slot] }
21
+ # type metadata = {
22
+ # version: Integer,
23
+ # trivia_policy: Symbol,
24
+ # kinds: kinds,
25
+ # slots: Hash[Integer, slot]
26
+ # }
27
+
28
+ TRIVIA_KINDS = %w[
29
+ whitespace
30
+ newline
31
+ line_comment
32
+ block_comment
33
+ custom_skip
34
+ skipped_tokens
35
+ ].freeze #: Array[String]
36
+
37
+ SYNTHETIC_KINDS = %w[
38
+ lexical_error_token
39
+ missing_token
40
+ error_node
41
+ source_file
42
+ synthetic_root
43
+ ].freeze #: Array[String]
44
+
45
+ # @rbs @grammar: IR::Grammar
46
+ # @rbs @trivia_policy: Symbol
47
+
48
+ # @rbs (IR::Grammar grammar, ?trivia_policy: Symbol | String) -> void
49
+ def initialize(grammar, trivia_policy: :leading)
50
+ @grammar = grammar
51
+ @trivia_policy = normalize_trivia_policy(trivia_policy)
52
+ end
53
+
54
+ # Return table-ready CST metadata with stable insertion order.
55
+ # @rbs () -> metadata
56
+ def build
57
+ names = @grammar.symbols.sort_by(&:id).map(&:name)
58
+ named = append_named_kinds(names)
59
+ trivia = append_kinds(names, TRIVIA_KINDS)
60
+ synthetic = append_kinds(names, SYNTHETIC_KINDS)
61
+ terminals = @grammar.terminals.map(&:id)
62
+ nonterminals = @grammar.nonterminals.map(&:id)
63
+ kinds = {
64
+ names: names.freeze,
65
+ terminal_range: half_open_range(terminals),
66
+ nonterminal_range: half_open_range(nonterminals),
67
+ named: named.freeze,
68
+ named_nonterminals: named_nonterminals(named).freeze,
69
+ trivia: trivia.freeze,
70
+ synthetic: synthetic.freeze
71
+ }.freeze #: kinds
72
+ metadata = { # rubocop:disable Style/RedundantAssignment -- preserves the exact record type for Steep.
73
+ version: 1, trivia_policy: @trivia_policy,
74
+ kinds: kinds, slots: slot_metadata(named).freeze
75
+ }.freeze #: metadata
76
+ metadata
77
+ end
78
+
79
+ private
80
+
81
+ # @rbs (Symbol | String policy) -> Symbol
82
+ def normalize_trivia_policy(policy)
83
+ normalized = policy.to_sym
84
+ normalized = :leading if normalized == :attach
85
+ return normalized if %i[leading balanced drop].include?(normalized)
86
+
87
+ raise ArgumentError, "cst_trivia must be :leading, :balanced, or :drop"
88
+ end
89
+
90
+ # @rbs (Array[String] names) -> Hash[String, Integer]
91
+ def append_named_kinds(names)
92
+ node_names = @grammar.productions.filter_map { |production| production.node&.fetch(:name) }.uniq.sort
93
+ append_kinds(names, node_names)
94
+ end
95
+
96
+ # @rbs (Array[String] names, Array[String] additions) -> Hash[String, Integer]
97
+ def append_kinds(names, additions)
98
+ additions.to_h do |name|
99
+ kind = names.length
100
+ names << name.freeze
101
+ [name.freeze, kind]
102
+ end
103
+ end
104
+
105
+ # @rbs (Array[Integer] ids) -> Array[Integer]
106
+ def half_open_range(ids)
107
+ return [0, 0].freeze if ids.empty?
108
+
109
+ [ids.min, ids.max + 1].freeze
110
+ end
111
+
112
+ # @rbs (Hash[String, Integer] named) -> Hash[Integer, Integer]
113
+ def named_nonterminals(named)
114
+ result = {} #: Hash[Integer, Integer]
115
+ @grammar.productions.each do |production|
116
+ node = production.node
117
+ next unless node
118
+
119
+ kind = named.fetch(node.fetch(:name))
120
+ current = result[kind]
121
+ if current && current != production.lhs
122
+ raise ArgumentError, "@node #{node.fetch(:name)} spans multiple nonterminals"
123
+ end
124
+
125
+ result[kind] = production.lhs
126
+ end
127
+ result
128
+ end
129
+
130
+ # @rbs (Hash[String, Integer] named) -> Hash[Integer, slot]
131
+ def slot_metadata(named)
132
+ @grammar.productions.filter_map do |production|
133
+ node = production.node
134
+ next unless node
135
+
136
+ fields = node.fetch(:fields).each_with_index.to_h do |name, index|
137
+ [name, field_slot(index, production.rhs.fetch(index))]
138
+ end.freeze
139
+ [
140
+ production.id,
141
+ {
142
+ node_kind: named.fetch(node.fetch(:name)),
143
+ node_name: node.fetch(:name),
144
+ fields: fields
145
+ }.freeze
146
+ ]
147
+ end.to_h
148
+ end
149
+
150
+ # @rbs (Integer index, Integer symbol_id) -> field_slot
151
+ def field_slot(index, symbol_id)
152
+ extraction = repetition_extraction(symbol_id)
153
+ return index unless extraction
154
+
155
+ value = { index: index, extraction: extraction } #: field_slot
156
+ value.freeze
157
+ end
158
+
159
+ # @rbs (Integer symbol_id) -> Symbol?
160
+ def repetition_extraction(symbol_id)
161
+ origins = @grammar.productions
162
+ .select { |production| production.lhs == symbol_id }
163
+ .map { |production| production.origin.fetch(:kind) }.uniq
164
+ return :separated_list if origins == [:separated_list_expansion]
165
+ return :repetition if (origins - %i[star_expansion plus_expansion]).empty? && !origins.empty?
166
+
167
+ nil
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "railroad"
4
+ require_relative "symbol_labels"
5
+
6
+ module Ibex
7
+ module Codegen
8
+ # Renders normalized user rules as deterministic Markdown, HTML, or railroad SVG.
9
+ module Documentation
10
+ FORMATS = %w[markdown html railroad].freeze #: Array[String]
11
+
12
+ class << self
13
+ # @rbs (IR::Grammar grammar, format: String | Symbol) -> String
14
+ def render(grammar, format:)
15
+ case format.to_s
16
+ when "markdown" then render_markdown(grammar)
17
+ when "html" then render_html(grammar)
18
+ when "railroad" then Railroad.render(grammar)
19
+ else raise ArgumentError, "unsupported documentation format #{format.inspect}"
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # @rbs (IR::Grammar grammar) -> String
26
+ def render_markdown(grammar)
27
+ labels = SymbolLabels.build(grammar)
28
+ lines = ["# #{markdown_escape(grammar.class_name)} grammar", ""]
29
+ rule_groups(grammar).each do |symbol, productions|
30
+ lines << "## #{markdown_code(labels.fetch(symbol.id))}"
31
+ lines << ""
32
+ append_markdown_documentation(lines, symbol.documentation)
33
+ lines << "Alternatives:"
34
+ lines << ""
35
+ productions.each do |production|
36
+ lines << "- #{markdown_alternative(grammar, production, labels)}"
37
+ end
38
+ lines << ""
39
+ end
40
+ "#{lines.join("\n").rstrip}\n"
41
+ end
42
+
43
+ # @rbs (Array[String] lines, String? documentation) -> void
44
+ def append_markdown_documentation(lines, documentation)
45
+ return unless documentation
46
+
47
+ documentation.split("\n", -1).each do |line|
48
+ escaped = markdown_escape(line)
49
+ lines << (escaped.empty? ? ">" : "> #{escaped}")
50
+ end
51
+ lines << ""
52
+ end
53
+
54
+ # @rbs (IR::Grammar grammar, IR::Production production, Hash[Integer, String] labels) -> String
55
+ def markdown_alternative(grammar, production, labels)
56
+ return "ε" if production.rhs.empty?
57
+
58
+ production.rhs.map do |symbol_id|
59
+ raise Ibex::Error, "missing grammar symbol id #{symbol_id}" unless grammar.symbol_by_id(symbol_id)
60
+
61
+ markdown_code(labels.fetch(symbol_id))
62
+ end.join(" ")
63
+ end
64
+
65
+ # @rbs (IR::Grammar grammar) -> String
66
+ def render_html(grammar)
67
+ labels = SymbolLabels.build(grammar)
68
+ title = "#{html_escape(grammar.class_name)} grammar"
69
+ lines = html_start(title)
70
+ rule_groups(grammar).each do |symbol, productions|
71
+ append_html_rule(lines, grammar, symbol, productions, labels)
72
+ end
73
+ lines.push(" </main>", "</body>", "</html>")
74
+ "#{lines.join("\n")}\n"
75
+ end
76
+
77
+ # @rbs (String title) -> Array[String]
78
+ def html_start(title)
79
+ [
80
+ "<!doctype html>",
81
+ "<html lang=\"en\">",
82
+ "<head>",
83
+ " <meta charset=\"utf-8\">",
84
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
85
+ " <title>#{title}</title>",
86
+ " <style>",
87
+ " body { color: #172033; background: #fff; font: 16px/1.6 system-ui, sans-serif; margin: 0; }",
88
+ " main { max-width: 72rem; margin: 0 auto; padding: 2rem; }",
89
+ " section { border-top: 1px solid #d8dee9; padding: 1.25rem 0; }",
90
+ " code { background: #f3f6fa; border-radius: .25rem; padding: .1rem .3rem; }",
91
+ " .documentation { white-space: normal; }",
92
+ " .alternatives { padding-left: 1.5rem; }",
93
+ " </style>",
94
+ "</head>",
95
+ "<body>",
96
+ " <main>",
97
+ " <h1>#{title}</h1>"
98
+ ]
99
+ end
100
+
101
+ # @rbs (Array[String] lines, IR::Grammar grammar, IR::GrammarSymbol symbol,
102
+ # Array[IR::Production] productions, Hash[Integer, String] labels) -> void
103
+ def append_html_rule(lines, grammar, symbol, productions, labels)
104
+ label = html_escape(labels.fetch(symbol.id))
105
+ lines << " <section aria-labelledby=\"rule-#{symbol.id}\">"
106
+ lines << " <h2 id=\"rule-#{symbol.id}\"><code>#{label}</code></h2>"
107
+ if symbol.documentation
108
+ documentation = symbol.documentation.split("\n", -1).map { |line| html_escape(line) }.join("<br>\n ")
109
+ lines << " <p class=\"documentation\">#{documentation}</p>"
110
+ end
111
+ lines << " <ol class=\"alternatives\" aria-label=\"Alternatives for #{label}\">"
112
+ productions.each do |production|
113
+ lines << " <li>#{html_alternative(grammar, production, labels)}</li>"
114
+ end
115
+ lines << " </ol>"
116
+ lines << " </section>"
117
+ end
118
+
119
+ # @rbs (IR::Grammar grammar, IR::Production production, Hash[Integer, String] labels) -> String
120
+ def html_alternative(grammar, production, labels)
121
+ return "<span aria-label=\"empty\">ε</span>" if production.rhs.empty?
122
+
123
+ production.rhs.map do |symbol_id|
124
+ raise Ibex::Error, "missing grammar symbol id #{symbol_id}" unless grammar.symbol_by_id(symbol_id)
125
+
126
+ "<code>#{html_escape(labels.fetch(symbol_id))}</code>"
127
+ end.join(" ")
128
+ end
129
+
130
+ # @rbs (IR::Grammar grammar) -> Array[[IR::GrammarSymbol, Array[IR::Production]]]
131
+ def rule_groups(grammar)
132
+ grouped = grammar.productions.select { |production| production.origin[:kind].to_s == "user" }.group_by(&:lhs)
133
+ grammar.nonterminals.filter_map do |symbol|
134
+ productions = grouped[symbol.id]
135
+ [symbol, productions] if productions
136
+ end
137
+ end
138
+
139
+ # @rbs (String value) -> String
140
+ def markdown_code(value)
141
+ safe = html_escape(value)
142
+ longest_run = safe.scan(/`+/).map(&:length).max.to_i
143
+ delimiter = "`" * (longest_run + 1)
144
+ padding = markdown_code_padding?(safe) ? " " : ""
145
+ "#{delimiter}#{padding}#{safe}#{padding}#{delimiter}"
146
+ end
147
+
148
+ # @rbs (String value) -> bool
149
+ def markdown_code_padding?(value)
150
+ return true if value.start_with?("`") || value.end_with?("`")
151
+
152
+ value.start_with?(" ") && value.end_with?(" ") && !value.match?(/\A +\z/)
153
+ end
154
+
155
+ # @rbs (String value) -> String
156
+ def markdown_escape(value)
157
+ html_escape(value).gsub(/([\\`*_{}\[\]()#+\-.!|>])/) { |match| "\\#{match}" }
158
+ end
159
+
160
+ # @rbs (String value) -> String
161
+ def html_escape(value)
162
+ safe = value.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
163
+ .gsub(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u, "\uFFFD")
164
+ safe.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
165
+ .gsub('"', "&quot;").gsub("'", "&#39;")
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lalr"
4
+ require_relative "symbol_labels"
5
+
6
+ module Ibex
7
+ module Codegen
8
+ # @rbs!
9
+ # type explain_selection = {
10
+ # state: IR::AutomatonState,
11
+ # conflict: IR::conflict,
12
+ # conflict_index: Integer
13
+ # }
14
+ #
15
+ # type explain_entry = {
16
+ # state: IR::AutomatonState,
17
+ # conflict: IR::conflict,
18
+ # example: IR::counterexample
19
+ # }
20
+
21
+ # Renders a selected conflict explanation from Automaton IR and counterexamples.
22
+ # rubocop:disable Metrics/ClassLength -- inline contracts and two stable output formats stay near selection policy.
23
+ class Explain
24
+ SCHEMA_VERSION = 1 #: Integer
25
+
26
+ # @rbs (IR::Automaton automaton, ?state: Integer?, ?token: String?, ?max_tokens: Integer,
27
+ # ?max_configurations: Integer) -> void
28
+ def initialize(automaton, state: nil, token: nil, max_tokens: LALR::Counterexample::DEFAULT_MAX_TOKENS,
29
+ max_configurations: LALR::Counterexample::DEFAULT_MAX_CONFIGURATIONS)
30
+ @automaton = automaton
31
+ @grammar = automaton.grammar
32
+ @state_selector = state
33
+ @token_query = token
34
+ @max_tokens = max_tokens
35
+ @max_configurations = max_configurations
36
+ @labels = SymbolLabels.build(@grammar)
37
+ @token_selector = resolve_token(token)
38
+ validate_state!
39
+ @entries = select_entries
40
+ end
41
+
42
+ # @rbs () -> Hash[Symbol, untyped]
43
+ def to_h
44
+ unifying = @entries.count { |entry| entry.fetch(:example).fetch(:unifying) }
45
+ {
46
+ ibex_explain: "conflicts",
47
+ schema_version: SCHEMA_VERSION,
48
+ algorithm: @automaton.algorithm,
49
+ selectors: {
50
+ state: @state_selector,
51
+ token: @token_selector && token_reference(@token_selector, query: @token_query)
52
+ },
53
+ search: { max_tokens: @max_tokens, max_configurations: @max_configurations },
54
+ summary: {
55
+ matched_conflicts: @entries.length,
56
+ unifying_counterexamples: unifying,
57
+ nonunifying_witnesses: @entries.length - unifying
58
+ },
59
+ conflicts: @entries.map { |entry| conflict_document(entry) }
60
+ }
61
+ end
62
+
63
+ # @rbs () -> String
64
+ def render_text
65
+ lines = [
66
+ "Ibex conflict explanation v#{SCHEMA_VERSION}",
67
+ "Algorithm: #{@automaton.algorithm}",
68
+ "State selector: #{@state_selector || 'all'}",
69
+ "Token selector: #{@token_selector ? token_label(@token_selector, query: @token_query) : 'all'}",
70
+ "Search budget: #{@max_tokens} tokens, #{@max_configurations} configurations",
71
+ "Matched conflicts: #{@entries.length}",
72
+ ""
73
+ ]
74
+ if @entries.empty?
75
+ lines << "No conflicts matched the selectors."
76
+ else
77
+ @entries.each_with_index { |entry, index| append_conflict(lines, entry, index + 1) }
78
+ end
79
+ "#{lines.join("\n")}\n"
80
+ end
81
+
82
+ private
83
+
84
+ # @rbs (String? query) -> IR::GrammarSymbol?
85
+ def resolve_token(query)
86
+ return unless query
87
+
88
+ canonical = @grammar.terminals.find { |symbol| symbol.name == query }
89
+ return canonical if canonical
90
+
91
+ displayed = @grammar.terminals.select { |symbol| symbol.display_name == query }
92
+ return displayed.first if displayed.one?
93
+
94
+ if displayed.length > 1
95
+ names = displayed.map(&:name).sort.join(", ")
96
+ raise Ibex::Error, "(cli):1:1: token display name #{query.inspect} is ambiguous; use one of: #{names}"
97
+ end
98
+
99
+ raise Ibex::Error,
100
+ "(cli):1:1: unknown token #{query.inspect}; use a grammar token name or an exact display name"
101
+ end
102
+
103
+ # @rbs () -> void
104
+ def validate_state!
105
+ return unless @state_selector
106
+ return if @state_selector >= 0 && @automaton.states.any? { |state| state.id == @state_selector }
107
+
108
+ raise Ibex::Error, "(cli):1:1: unknown automaton state #{@state_selector}"
109
+ end
110
+
111
+ # @rbs () -> Array[explain_entry]
112
+ def select_entries
113
+ selections = selected_conflicts
114
+ return [] if selections.empty?
115
+
116
+ counterexamples = LALR::Counterexample.new(
117
+ @automaton, max_tokens: @max_tokens, max_configurations: @max_configurations
118
+ )
119
+ selections.map do |selection|
120
+ state = selection.fetch(:state)
121
+ {
122
+ state: state,
123
+ conflict: selection.fetch(:conflict),
124
+ example: counterexamples.for_conflict(state.id, selection.fetch(:conflict_index))
125
+ }
126
+ end
127
+ end
128
+
129
+ # @rbs () -> Array[explain_selection]
130
+ def selected_conflicts
131
+ @automaton.states.flat_map do |state|
132
+ state.conflicts.each_with_index.filter_map do |conflict, conflict_index|
133
+ next if @state_selector && state.id != @state_selector
134
+ next if @token_selector && conflict[:symbol] != @token_selector.name
135
+
136
+ { state: state, conflict: conflict, conflict_index: conflict_index }
137
+ end
138
+ end
139
+ end
140
+
141
+ # @rbs (explain_entry entry) -> Hash[Symbol, untyped]
142
+ def conflict_document(entry)
143
+ state = entry.fetch(:state)
144
+ conflict = entry.fetch(:conflict)
145
+ example = entry.fetch(:example)
146
+ document = {
147
+ state: state.id,
148
+ type: conflict.fetch(:type).to_s,
149
+ token: symbol_reference(conflict.fetch(:symbol)),
150
+ alternatives: conflict_alternatives(conflict),
151
+ resolution: string_values(conflict.fetch(:resolution)),
152
+ witness: {
153
+ kind: example.fetch(:unifying) ? "unifying_counterexample" : "nonunifying_witness",
154
+ sentence: example.fetch(:sentence).map { |name| symbol_reference(name) },
155
+ lookahead_index: example.fetch(:lookahead_index),
156
+ symbol_path: example.fetch(:symbol_path).map { |name| symbol_reference(name) },
157
+ interpretations: example.fetch(:interpretations).map { |item| interpretation_document(item) }
158
+ }
159
+ } #: Hash[Symbol, untyped]
160
+ document[:midrule_origins] = conflict[:midrule_origins] if conflict[:midrule_origins]
161
+ document
162
+ end
163
+
164
+ # @rbs (IR::conflict conflict) -> Array[Hash[Symbol, untyped]]
165
+ def conflict_alternatives(conflict)
166
+ case conflict[:type]
167
+ when :shift_reduce
168
+ [{ kind: "shift", state: conflict.fetch(:shift_to) },
169
+ { kind: "reduce", production: conflict.fetch(:reduce) }]
170
+ when :reduce_reduce
171
+ reduce_reduce = conflict #: IR::reduce_reduce_conflict
172
+ reduce_reduce[:reductions].map { |production| { kind: "reduce", production: production } }
173
+ else
174
+ []
175
+ end
176
+ end
177
+
178
+ # @rbs (IR::interpretation interpretation) -> Hash[Symbol, untyped]
179
+ def interpretation_document(interpretation)
180
+ value = { kind: interpretation.fetch(:kind).to_s,
181
+ tree: tree_document(interpretation.fetch(:tree)) } #: Hash[Symbol, untyped]
182
+ value[:state] = interpretation[:state] if interpretation.key?(:state)
183
+ value[:production] = interpretation[:production] if interpretation.key?(:production)
184
+ value
185
+ end
186
+
187
+ # @rbs (untyped tree) -> untyped
188
+ def tree_document(tree)
189
+ return symbol_reference(tree.to_s) unless tree.is_a?(Hash)
190
+
191
+ value = {} #: Hash[Symbol, untyped]
192
+ value[:symbol] = symbol_reference(tree[:symbol]) if tree[:symbol]
193
+ value[:token] = symbol_reference(tree[:token]) if tree[:token]
194
+ value[:production] = tree[:production] if tree[:production]
195
+ value[:children] = tree[:children].map { |child| tree_document(child) } if tree[:children]
196
+ value
197
+ end
198
+
199
+ # @rbs (Hash[Symbol, untyped] value) -> Hash[Symbol, untyped]
200
+ def string_values(value)
201
+ value.transform_values { |item| item.is_a?(Symbol) ? item.to_s : item }
202
+ end
203
+
204
+ # @rbs (String name) -> Hash[Symbol, untyped]
205
+ def symbol_reference(name)
206
+ symbol = @grammar.symbol(name)
207
+ return { name: name, display_name: nil, label: name } unless symbol
208
+
209
+ token_reference(symbol)
210
+ end
211
+
212
+ # @rbs (IR::GrammarSymbol symbol, ?query: String?) -> Hash[Symbol, untyped]
213
+ def token_reference(symbol, query: nil)
214
+ value = { name: symbol.name, display_name: symbol.display_name,
215
+ label: @labels.fetch(symbol.id, symbol.name) } #: Hash[Symbol, untyped]
216
+ value[:query] = query if query
217
+ value
218
+ end
219
+
220
+ # @rbs (Array[String] lines, explain_entry entry, Integer number) -> void
221
+ def append_conflict(lines, entry, number)
222
+ state = entry.fetch(:state)
223
+ conflict = entry.fetch(:conflict)
224
+ example = entry.fetch(:example)
225
+ token = @grammar.symbol(conflict.fetch(:symbol))
226
+ token_text = token ? token_label(token) : conflict.fetch(:symbol)
227
+ type = conflict.fetch(:type).to_s.tr("_", "/")
228
+ lines << "Conflict #{number}: state #{state.id}, #{type}, token #{token_text}"
229
+ append_midrule_origins(lines, conflict)
230
+ append_witness_steps(lines, state, conflict, example)
231
+ append_interpretations(lines, example)
232
+ unless example.fetch(:unifying)
233
+ lines << " The search found no shared sentence within the configured budget; this witness is deterministic."
234
+ end
235
+ lines << ""
236
+ end
237
+
238
+ # @rbs (Array[String] lines, IR::conflict conflict) -> void
239
+ def append_midrule_origins(lines, conflict)
240
+ origins = conflict[:midrule_origins]
241
+ return unless origins
242
+
243
+ origins.each do |origin|
244
+ lines << " Midrule action origin: #{origin.fetch(:file)}:#{origin.fetch(:line)}:#{origin.fetch(:column)}"
245
+ end
246
+ end
247
+
248
+ # @rbs (Array[String] lines, IR::AutomatonState state, IR::conflict conflict,
249
+ # IR::counterexample example) -> void
250
+ def append_witness_steps(lines, state, conflict, example)
251
+ lines << " 1. Reach state #{state.id} with the witness prefix."
252
+ sentence = example.fetch(:sentence).map { |name| display_name(name) }
253
+ sentence.insert(example.fetch(:lookahead_index), "•")
254
+ kind = example.fetch(:unifying) ? "Unifying counterexample" : "Nonunifying reachability witness"
255
+ lines << " #{kind}: #{sentence.join(' ')}"
256
+ lines << " 2. The lookahead permits these competing actions:"
257
+ conflict_alternatives(conflict).each { |alternative| lines << " - #{alternative_text(alternative)}" }
258
+ resolution = conflict.fetch(:resolution)
259
+ lines << " 3. Resolution: #{resolution.fetch(:by)} chose #{resolution.fetch(:chose)}."
260
+ end
261
+
262
+ # @rbs (Array[String] lines, IR::counterexample example) -> void
263
+ def append_interpretations(lines, example)
264
+ lines << " Competing derivations:"
265
+ example.fetch(:interpretations).each_with_index do |interpretation, index|
266
+ lines << " #{index + 1}. #{interpretation.fetch(:kind)}"
267
+ append_tree(lines, interpretation.fetch(:tree), " ", "")
268
+ end
269
+ end
270
+
271
+ # @rbs (Hash[Symbol, untyped] alternative) -> String
272
+ def alternative_text(alternative)
273
+ return "shift to state #{alternative.fetch(:state)}" if alternative.fetch(:kind) == "shift"
274
+
275
+ "reduce by production #{alternative.fetch(:production)}"
276
+ end
277
+
278
+ # @rbs (Array[String] lines, untyped tree, String prefix, String connector) -> void
279
+ def append_tree(lines, tree, prefix, connector)
280
+ unless tree.is_a?(Hash)
281
+ lines << "#{prefix}#{connector}#{display_name(tree.to_s)}"
282
+ return
283
+ end
284
+
285
+ name = tree[:symbol] || tree[:token]
286
+ production = tree[:production] ? " (production #{tree[:production]})" : ""
287
+ lines << "#{prefix}#{connector}#{display_name(name.to_s)}#{production}"
288
+ children = tree.fetch(:children, [])
289
+ continuation = { "" => "", "|- " => "| " }.fetch(connector, " ")
290
+ child_prefix = "#{prefix}#{continuation}"
291
+ children.each_with_index do |child, index|
292
+ branch = index == children.length - 1 ? "`- " : "|- "
293
+ append_tree(lines, child, child_prefix, branch)
294
+ end
295
+ end
296
+
297
+ # @rbs (IR::GrammarSymbol symbol, ?query: String?) -> String
298
+ def token_label(symbol, query: nil)
299
+ label = if symbol.display_name && symbol.display_name != symbol.name
300
+ "#{symbol.name} (display #{symbol.display_name.inspect})"
301
+ else
302
+ symbol.name
303
+ end
304
+ query && query != symbol.name ? "#{label}, selected by #{query.inspect}" : label
305
+ end
306
+
307
+ # @rbs (String name) -> String
308
+ def display_name(name) = @grammar.symbol(name)&.then { |symbol| @labels.fetch(symbol.id, symbol.name) } || name
309
+ end
310
+ # rubocop:enable Metrics/ClassLength
311
+ end
312
+ end