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
@@ -2,18 +2,57 @@
2
2
 
3
3
  module Ibex
4
4
  module Frontend
5
+ # Keeps the public Rule constructor compatible with ASTs built before parameters and inline rules existed.
6
+ module ASTRuleDefaults
7
+ # @rbs (*untyped arguments, **untyped keywords) -> void
8
+ def initialize(*arguments, **keywords)
9
+ if arguments.empty?
10
+ keywords = keywords.merge(parameters: keywords[:parameters] || [], inline: keywords[:inline] || false)
11
+ elsif arguments.one? && arguments.first.is_a?(Hash)
12
+ attributes = arguments.first
13
+ arguments = [
14
+ attributes.merge(parameters: attributes[:parameters] || [], inline: attributes[:inline] || false)
15
+ ]
16
+ end
17
+ super(*arguments, **keywords) # rubocop:disable Style/SuperArguments
18
+ end
19
+ end
20
+
5
21
  # Grammar frontend node types.
22
+ # rubocop:disable Metrics/ModuleLength -- the public node vocabulary is intentionally kept together.
6
23
  module AST
7
24
  # @rbs!
8
- # type declaration = Tokens | Precedence | Options | Expect | Start | Convert
9
- # type item = SymbolReference | InlineAction | Optional | Star | Plus | Group | SeparatedList
25
+ # type symbol_metadata = DisplayName | SemanticType
26
+ # type declaration = Include | Tokens | Precedence | Options | Expect | ExpectRR | Start | Recovery |
27
+ # OnErrorReduce | GrammarTest | Lexer | Convert | Parameter | Printer | symbol_metadata
28
+ # type lexer_entry = LexerRule | LexerState
29
+ # type item = SymbolReference | ParameterizedReference | InlineAction | Optional | Star | Plus | Group |
30
+ # SeparatedList | Empty
10
31
  # type user_code = Hash[String, Array[UserCode]]
32
+ # class Rule < Struct[String | Array[String] | Array[Alternative] | Location | String? | bool]
33
+ # attr_accessor lhs: String
34
+ # attr_accessor parameters: Array[String]
35
+ # attr_accessor alternatives: Array[Alternative]
36
+ # attr_accessor loc: Location
37
+ # attr_accessor documentation: String?
38
+ # attr_accessor inline: bool
39
+ # def self.new: (?lhs: String, ?parameters: Array[String]?, ?alternatives: Array[Alternative],
40
+ # ?loc: Location, ?documentation: String?, ?inline: bool?) -> instance
41
+ # | ({ ?lhs: String, ?parameters: Array[String]?, ?alternatives: Array[Alternative],
42
+ # ?loc: Location, ?documentation: String?, ?inline: bool? }) -> instance
43
+ # end
11
44
 
12
45
  # Adds deterministic, recursively serializable hashes to Struct nodes.
13
46
  # @rbs module-self Struct[untyped]
14
47
  module Node
15
48
  def to_h
16
49
  fields = each_pair.to_h { |name, value| [name, serialize(value)] }
50
+ fields.delete(:aliases) if self.class.name.end_with?("::Tokens") && !fields[:aliases]
51
+ if self.class.name.end_with?("::Root")
52
+ fields.delete(:extended) unless fields[:extended]
53
+ fields.delete(:cst) unless fields[:cst]
54
+ end
55
+ fields.delete(:node_annotation) if self.class.name.end_with?("::Alternative") && !fields[:node_annotation]
17
56
  { node: self.class.name.split("::").last }.merge(fields)
18
57
  end
19
58
 
@@ -37,12 +76,24 @@ module Ibex
37
76
  :rules, #: Array[Rule]
38
77
  :user_code, #: user_code
39
78
  :loc, #: Location
79
+ :extended, #: bool?
80
+ :cst, #: bool?
40
81
  keyword_init: true
41
- ) do
42
- include Node
43
- end
82
+ ) { include Node }
83
+ Fragment = Struct.new(
84
+ :declarations, #: Array[declaration]
85
+ :rules, #: Array[Rule]
86
+ :loc, #: Location
87
+ keyword_init: true
88
+ ) { include Node }
89
+ Include = Struct.new(
90
+ :path, #: String
91
+ :loc, #: Location
92
+ keyword_init: true
93
+ ) { include Node }
44
94
  Tokens = Struct.new(
45
95
  :names, #: Array[String]
96
+ :aliases, #: Hash[String, String]?
46
97
  :loc, #: Location
47
98
  keyword_init: true
48
99
  ) { include Node }
@@ -68,8 +119,49 @@ module Ibex
68
119
  :loc, #: Location
69
120
  keyword_init: true
70
121
  ) { include Node }
122
+ ExpectRR = Struct.new(
123
+ :conflicts, #: Integer
124
+ :loc, #: Location
125
+ keyword_init: true
126
+ ) { include Node }
71
127
  Start = Struct.new(
128
+ :names, #: Array[String]
129
+ :loc, #: Location
130
+ keyword_init: true
131
+ ) { include Node }
132
+ Recovery = Struct.new(
133
+ :sync_tokens, #: Array[String]
134
+ :loc, #: Location
135
+ keyword_init: true
136
+ ) { include Node }
137
+ OnErrorReduce = Struct.new(
138
+ :names, #: Array[String]
139
+ :loc, #: Location
140
+ keyword_init: true
141
+ ) { include Node }
142
+ GrammarTest = Struct.new(
143
+ :expectation, #: Symbol
144
+ :source, #: String
145
+ :loc, #: Location
146
+ keyword_init: true
147
+ ) { include Node }
148
+ Lexer = Struct.new(
149
+ :definitions, #: Array[lexer_entry]
150
+ :loc, #: Location
151
+ keyword_init: true
152
+ ) { include Node }
153
+ LexerRule = Struct.new(
154
+ :kind, #: Symbol
155
+ :token, #: String?
156
+ :pattern, #: String
157
+ :pattern_kind, #: Symbol
158
+ :action, #: String?
159
+ :loc, #: Location
160
+ keyword_init: true
161
+ ) { include Node }
162
+ LexerState = Struct.new(
72
163
  :name, #: String
164
+ :definitions, #: Array[lexer_entry]
73
165
  :loc, #: Location
74
166
  keyword_init: true
75
167
  ) { include Node }
@@ -84,16 +176,51 @@ module Ibex
84
176
  :loc, #: Location
85
177
  keyword_init: true
86
178
  ) { include Node }
179
+ DisplayName = Struct.new(
180
+ :name, #: String
181
+ :value, #: String
182
+ :loc, #: Location
183
+ keyword_init: true
184
+ ) { include Node }
185
+ SemanticType = Struct.new(
186
+ :name, #: String
187
+ :value, #: String
188
+ :loc, #: Location
189
+ keyword_init: true
190
+ ) { include Node }
191
+ Parameter = Struct.new(
192
+ :name, #: String
193
+ :semantic_type, #: String?
194
+ :loc, #: Location
195
+ keyword_init: true
196
+ ) { include Node }
197
+ Printer = Struct.new(
198
+ :name, #: String
199
+ :code, #: String
200
+ :loc, #: Location
201
+ keyword_init: true
202
+ ) { include Node }
203
+ # @rbs skip
87
204
  Rule = Struct.new(
88
205
  :lhs, #: String
206
+ :parameters, #: Array[String]
89
207
  :alternatives, #: Array[Alternative]
90
208
  :loc, #: Location
209
+ :documentation, #: String?
210
+ :inline, #: bool
91
211
  keyword_init: true
92
- ) { include Node }
212
+ ) { include Node, ASTRuleDefaults }
93
213
  Alternative = Struct.new(
94
214
  :items, #: Array[item]
95
215
  :action, #: InlineAction?
96
216
  :precedence, #: String?
217
+ :node_annotation, #: NodeAnnotation?
218
+ :loc, #: Location
219
+ keyword_init: true
220
+ ) { include Node }
221
+ NodeAnnotation = Struct.new(
222
+ :name, #: String
223
+ :fields, #: Array[String]
97
224
  :loc, #: Location
98
225
  keyword_init: true
99
226
  ) { include Node }
@@ -103,11 +230,22 @@ module Ibex
103
230
  :loc, #: Location
104
231
  keyword_init: true
105
232
  ) { include Node }
233
+ ParameterizedReference = Struct.new(
234
+ :name, #: String
235
+ :arguments, #: Array[item]
236
+ :named_reference, #: String?
237
+ :loc, #: Location
238
+ keyword_init: true
239
+ ) { include Node }
106
240
  InlineAction = Struct.new(
107
241
  :code, #: String
108
242
  :loc, #: Location
109
243
  keyword_init: true
110
244
  ) { include Node }
245
+ Empty = Struct.new(
246
+ :loc, #: Location
247
+ keyword_init: true
248
+ ) { include Node }
111
249
  Optional = Struct.new(
112
250
  :item, #: item
113
251
  :loc, #: Location
@@ -142,5 +280,6 @@ module Ibex
142
280
  keyword_init: true
143
281
  ) { include Node }
144
282
  end
283
+ # rubocop:enable Metrics/ModuleLength
145
284
  end
146
285
  end
@@ -6,18 +6,20 @@ module Ibex
6
6
  class BootstrapParser
7
7
  include BootstrapParserDeclarations
8
8
  include BootstrapParserRules
9
+ include BootstrapParserParameters
9
10
 
10
11
  # @rbs @tokens: Array[Token]
11
12
  # @rbs @index: Integer
12
13
  # @rbs @mode: Symbol
13
14
 
14
15
  # @rbs (String | Array[Token] source, ?file: String, ?mode: Symbol) -> void
15
- def initialize(source, file: "(grammar)", mode: :racc)
16
- raise ArgumentError, "mode must be :racc or :extended" unless %i[racc extended].include?(mode)
16
+ def initialize(source, file: "(grammar)", mode: :default)
17
+ raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode)
17
18
 
18
19
  @tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
19
20
  @index = 0
20
21
  @mode = mode
22
+ @pragmas = {} #: Hash[String, bool]
21
23
  end
22
24
 
23
25
  # @rbs () -> AST::Root
@@ -29,15 +31,56 @@ module Ibex
29
31
  declarations = parse_declarations
30
32
  expect_keyword("rule")
31
33
  rules = parse_rules
32
- expect_keyword("end")
34
+ expect_keyword("end") unless current.type == :user_code
33
35
  user_code = parse_user_code
34
36
  expect(:eof)
35
37
  AST::Root.new(class_name: class_name, superclass: superclass, declarations: declarations,
36
- rules: rules, user_code: user_code, loc: location)
38
+ rules: rules, user_code: user_code, loc: location,
39
+ extended: @mode == :extended || @pragmas.key?("extended") || @pragmas.key?("cst"),
40
+ cst: @pragmas.key?("cst"))
41
+ end
42
+
43
+ # @rbs () -> AST::Fragment
44
+ def parse_fragment
45
+ location = expect_keyword("fragment").location
46
+ extended_only!(location, "fragments")
47
+ declarations = parse_declarations
48
+ reject_fragment_root_declarations(declarations)
49
+ expect_keyword("rule")
50
+ rules = keyword?("end") ? Array.new(0) : parse_rules #: Array[AST::Rule]
51
+ expect_keyword("end")
52
+ user_code = parse_user_code
53
+ fail_at(user_code.values.flatten.first.loc, "user code is not allowed in fragments") unless user_code.empty?
54
+ expect(:eof)
55
+ AST::Fragment.new(declarations: declarations, rules: rules, loc: location)
37
56
  end
38
57
 
39
58
  private
40
59
 
60
+ # @rbs (Array[AST::declaration] declarations) -> void
61
+ def reject_fragment_root_declarations(declarations)
62
+ root_only = declarations.find { |declaration| fragment_root_declaration_name(declaration) }
63
+ return unless root_only
64
+
65
+ name = fragment_root_declaration_name(root_only)
66
+ fail_at(root_only.loc, "#{name} declarations are not allowed in fragments")
67
+ end
68
+
69
+ # @rbs (AST::declaration declaration) -> String?
70
+ def fragment_root_declaration_name(declaration)
71
+ return "options" if declaration.is_a?(AST::Options)
72
+ return "expect" if declaration.is_a?(AST::Expect)
73
+ return "%expect-rr" if declaration.is_a?(AST::ExpectRR)
74
+ return "%param" if declaration.is_a?(AST::Parameter)
75
+ return "start" if declaration.is_a?(AST::Start)
76
+ return "%recover" if declaration.is_a?(AST::Recovery)
77
+ return "%on_error_reduce" if declaration.is_a?(AST::OnErrorReduce)
78
+ return "%test" if declaration.is_a?(AST::GrammarTest)
79
+ return "lexer" if declaration.is_a?(AST::Lexer)
80
+
81
+ nil
82
+ end
83
+
41
84
  # @rbs () -> String
42
85
  def parse_constant_path
43
86
  parts = [token_string(expect(:identifier))]
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # An immutable machine-readable frontend error.
6
+ class Diagnostic
7
+ attr_reader :code #: String
8
+ attr_reader :phase #: Symbol
9
+ attr_reader :message #: String
10
+ attr_reader :location #: Location
11
+ attr_reader :span #: SourceSpan?
12
+ attr_reader :expected #: Array[String]
13
+ attr_reader :received #: String?
14
+
15
+ # @rbs (code: String, phase: Symbol, message: String, location: Location, ?span: SourceSpan?,
16
+ # ?expected: Array[String], ?received: String?, ?rendered: String?) -> void
17
+ def initialize(code:, phase:, message:, location:, span: nil, expected: [], received: nil, rendered: nil)
18
+ @code = code.dup.freeze
19
+ @phase = phase
20
+ @message = message.dup.freeze
21
+ @location = Location.new(
22
+ file: location.file.dup.freeze, line: location.line, column: location.column
23
+ ).freeze
24
+ @span = span
25
+ @expected = expected.map { |value| value.dup.freeze }.freeze
26
+ @received = received&.dup&.freeze
27
+ @rendered = (rendered || "#{location}: #{message}").dup.freeze
28
+ freeze
29
+ end
30
+
31
+ # @rbs () -> String
32
+ def severity
33
+ "error"
34
+ end
35
+
36
+ # @rbs () -> String
37
+ def to_s
38
+ @rendered
39
+ end
40
+
41
+ # @rbs () -> Hash[Symbol, untyped]
42
+ def to_h
43
+ {
44
+ code: code,
45
+ severity: severity,
46
+ phase: phase.to_s,
47
+ message: message,
48
+ location: location.to_h,
49
+ span: span&.to_h,
50
+ expected: expected,
51
+ received: received
52
+ }
53
+ end
54
+ end
55
+
56
+ # Immutable output from a diagnostic parse.
57
+ class ParseResult
58
+ attr_reader :diagnostics #: Array[Diagnostic]
59
+ attr_reader :ast #: AST::Root?
60
+ attr_reader :document #: SourceDocument?
61
+
62
+ # @rbs (diagnostics: Array[Diagnostic], ast: AST::Root?, document: SourceDocument?) -> void
63
+ def initialize(diagnostics:, ast:, document:)
64
+ @diagnostics = diagnostics.dup.freeze
65
+ @ast = ast
66
+ @document = document
67
+ freeze
68
+ end
69
+
70
+ # @rbs () -> bool
71
+ def success?
72
+ diagnostics.empty? && !ast.nil?
73
+ end
74
+
75
+ # @rbs () -> bool
76
+ def partial?
77
+ diagnostics.any? && !ast.nil?
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Re-runs the generated parser after suppressing only conservative source regions.
6
+ class DiagnosticRecovery
7
+ DECLARATION_STARTS = %w[
8
+ pragma include import token prechigh preclow options expect expect_rr start recover on_error_reduce test lexer
9
+ convert display type param printer
10
+ ].freeze #: Array[String]
11
+
12
+ # @rbs @tokens: Array[Token]
13
+ # @rbs @mode: Symbol
14
+ # @rbs @max_diagnostics: Integer
15
+ # @rbs @suppressed: Hash[Integer, bool]
16
+
17
+ # @rbs (Array[Token] tokens, mode: Symbol, max_diagnostics: Integer) -> void
18
+ def initialize(tokens, mode:, max_diagnostics:)
19
+ @tokens = tokens
20
+ @mode = mode
21
+ @max_diagnostics = max_diagnostics
22
+ @diagnostics = [] #: Array[Diagnostic]
23
+ @suppressed = {} #: Hash[Integer, bool]
24
+ end
25
+
26
+ # @rbs () -> [AST::Root | AST::Fragment | nil, Array[Diagnostic]]
27
+ def parse
28
+ ast = parse_until_stable
29
+ [ast, sorted_diagnostics]
30
+ end
31
+
32
+ private
33
+
34
+ # @rbs () -> (AST::Root | AST::Fragment | nil)
35
+ def parse_until_stable
36
+ attempts = 0
37
+ loop do
38
+ parser = GeneratedParser.new(working_tokens, mode: @mode)
39
+ begin
40
+ return parser.parse
41
+ rescue Ibex::Error => e
42
+ return if @diagnostics.length >= @max_diagnostics
43
+
44
+ diagnostic = diagnostic_from(e, parser.diagnostic_token, phase: :syntax)
45
+ append_diagnostic(diagnostic)
46
+ range = suppression_range(parser.diagnostic_token, e)
47
+ return unless range && suppress(range)
48
+
49
+ attempts += 1
50
+ return if attempts >= @tokens.length
51
+ end
52
+ end
53
+ end
54
+
55
+ # @rbs () -> Array[Token]
56
+ def working_tokens
57
+ @tokens.each_with_index.filter_map { |token, index| token unless @suppressed[index] }
58
+ end
59
+
60
+ # @rbs (Diagnostic diagnostic) -> void
61
+ def append_diagnostic(diagnostic)
62
+ key = diagnostic_key(diagnostic)
63
+ return if @diagnostics.any? { |existing| diagnostic_key(existing) == key }
64
+
65
+ @diagnostics << diagnostic
66
+ end
67
+
68
+ # @rbs (Diagnostic diagnostic) -> [String, String, Integer, Integer, String]
69
+ def diagnostic_key(diagnostic)
70
+ location = diagnostic.location
71
+ [diagnostic.phase.to_s, location.file, location.line, location.column, diagnostic.message]
72
+ end
73
+
74
+ # @rbs () -> Array[Diagnostic]
75
+ def sorted_diagnostics
76
+ @diagnostics.uniq { |diagnostic| diagnostic_key(diagnostic) }
77
+ .sort_by { |diagnostic| diagnostic_sort_key(diagnostic) }
78
+ .first(@max_diagnostics)
79
+ .freeze
80
+ end
81
+
82
+ # @rbs (Diagnostic diagnostic) -> [String, Integer, Integer, String, String]
83
+ def diagnostic_sort_key(diagnostic)
84
+ location = diagnostic.location
85
+ [location.file, location.line, location.column, diagnostic.phase.to_s, diagnostic.code]
86
+ end
87
+
88
+ # @rbs (Token? token, Exception error) -> Range[Integer]?
89
+ def suppression_range(token, error)
90
+ index = token_index(token) || token_index_for_message(error.message)
91
+ return unless index
92
+
93
+ rule_marker = rule_marker_index
94
+ return declaration_range(index, rule_marker) if rule_marker && index < rule_marker
95
+ return rule_range(index, rule_marker) if rule_marker
96
+
97
+ nil
98
+ end
99
+
100
+ # @rbs (Token? token) -> Integer?
101
+ def token_index(token)
102
+ return unless token
103
+
104
+ @tokens.index { |candidate| candidate.equal?(token) }
105
+ end
106
+
107
+ # @rbs (String message) -> Integer?
108
+ def token_index_for_message(message)
109
+ @tokens.index { |token| message.start_with?("#{token.location}:") }
110
+ end
111
+
112
+ # @rbs () -> Integer?
113
+ def rule_marker_index
114
+ @tokens.index { |token| token.type == :identifier && token.value == "rule" }
115
+ end
116
+
117
+ # @rbs (Integer failure, Integer rule_marker) -> Range[Integer]?
118
+ def declaration_range(failure, rule_marker)
119
+ starts = (0...rule_marker).select { |index| declaration_start?(index) }
120
+ start = starts.reverse.find { |index| index <= failure }
121
+ return unless start
122
+
123
+ finish = declaration_finish(start, rule_marker, starts)
124
+ start...finish
125
+ end
126
+
127
+ # @rbs (Integer index) -> bool
128
+ def declaration_start?(index)
129
+ token = @tokens[index]
130
+ token&.type == :identifier && DECLARATION_STARTS.include?(token.value)
131
+ end
132
+
133
+ # @rbs (Integer start, Integer rule_marker, Array[Integer] starts) -> Integer
134
+ def declaration_finish(start, rule_marker, starts)
135
+ value = @tokens.fetch(start).value
136
+ return matching_declaration_end(start, rule_marker, value == "prechigh" ? "preclow" : "prechigh") if
137
+ %w[prechigh preclow].include?(value)
138
+ return matching_declaration_end(start, rule_marker, "end") if %w[convert lexer].include?(value)
139
+
140
+ starts.find { |index| index > start } || rule_marker
141
+ end
142
+
143
+ # @rbs (Integer start, Integer fallback, String closer) -> Integer
144
+ def matching_declaration_end(start, fallback, closer)
145
+ index = ((start + 1)...fallback).find { |candidate| @tokens[candidate]&.value == closer }
146
+ index ? index + 1 : fallback
147
+ end
148
+
149
+ # @rbs (Integer failure, Integer rule_marker) -> Range[Integer]?
150
+ def rule_range(failure, rule_marker)
151
+ grammar_end = grammar_end_index(rule_marker)
152
+ definitions = rule_definition_starts(rule_marker, grammar_end)
153
+ start = definitions.reverse.find { |index| index <= failure }
154
+ return unless start
155
+
156
+ finish = definitions.find { |index| index > start } || grammar_end
157
+ alternative_range(failure, start, finish) || (start...finish)
158
+ end
159
+
160
+ # @rbs (Integer rule_marker) -> Integer
161
+ def grammar_end_index(rule_marker)
162
+ depth = 0
163
+ ((rule_marker + 1)...@tokens.length).each do |index|
164
+ token = @tokens.fetch(index)
165
+ depth += 1 if token.type == :"("
166
+ depth -= 1 if token.type == :")" && depth.positive?
167
+ return index if depth.zero? && token.type == :identifier && token.value == "end"
168
+ end
169
+ @tokens.length - 1
170
+ end
171
+
172
+ # @rbs (Integer rule_marker, Integer grammar_end) -> Array[Integer]
173
+ def rule_definition_starts(rule_marker, grammar_end)
174
+ starts = [] #: Array[Integer]
175
+ depth = 0
176
+ lhs_column = nil #: Integer?
177
+ ((rule_marker + 1)...grammar_end).each do |index|
178
+ token = @tokens.fetch(index)
179
+ depth += 1 if token.type == :"("
180
+ depth -= 1 if token.type == :")" && depth.positive?
181
+ next unless depth.zero? && lhs_candidate?(index, lhs_column)
182
+
183
+ lhs_column ||= token.location.column
184
+ starts << index
185
+ end
186
+ starts
187
+ end
188
+
189
+ # @rbs (Integer index, Integer? lhs_column) -> bool
190
+ def lhs_candidate?(index, lhs_column)
191
+ token = @tokens[index]
192
+ following = @tokens[index + 1]
193
+ return false unless token&.type == :identifier && following&.type == :":"
194
+
195
+ lhs_column.nil? || token.location.column <= lhs_column
196
+ end
197
+
198
+ # @rbs (Integer failure, Integer start, Integer finish) -> Range[Integer]?
199
+ def alternative_range(failure, start, finish)
200
+ colon = ((start + 1)...finish).find { |index| @tokens[index]&.type == :":" }
201
+ return unless colon
202
+
203
+ pipes = top_level_pipes(colon + 1, finish)
204
+ return if pipes.empty?
205
+
206
+ previous = pipes.reverse.find { |index| index < failure }
207
+ following = pipes.find { |index| index >= failure }
208
+ alternative_slice(previous, following, colon, finish)
209
+ end
210
+
211
+ # @rbs (Integer? previous, Integer? following, Integer colon, Integer finish) -> Range[Integer]?
212
+ def alternative_slice(previous, following, colon, finish)
213
+ if previous
214
+ following ? (previous...following) : (previous...finish)
215
+ elsif following
216
+ (colon + 1)..following
217
+ end
218
+ end
219
+
220
+ # @rbs (Integer start, Integer finish) -> Array[Integer]
221
+ def top_level_pipes(start, finish)
222
+ depth = 0
223
+ (start...finish).filter_map do |index|
224
+ token = @tokens.fetch(index)
225
+ depth += 1 if token.type == :"("
226
+ depth -= 1 if token.type == :")" && depth.positive?
227
+ index if depth.zero? && token.type == :|
228
+ end
229
+ end
230
+
231
+ # @rbs (Range[Integer] range) -> bool
232
+ def suppress(range)
233
+ changed = false
234
+ range.each do |index|
235
+ next if index.negative? || index >= @tokens.length || @tokens[index]&.type == :eof
236
+ next if @suppressed[index]
237
+
238
+ @suppressed[index] = true
239
+ changed = true
240
+ end
241
+ changed
242
+ end
243
+
244
+ # @rbs (Exception error, Token? token, phase: Symbol) -> Diagnostic
245
+ def diagnostic_from(error, token, phase:)
246
+ matching = @tokens.find { |candidate| error.message.start_with?("#{candidate.location}:") }
247
+ token = matching || token || @tokens.last
248
+ location = token&.location || Location.new(file: "(grammar)", line: 1, column: 1)
249
+ prefix = "#{location}: "
250
+ message = error.message.delete_prefix(prefix)
251
+ expected, received = expected_and_received(message)
252
+ Diagnostic.new(code: "frontend.#{phase}_error", phase: phase, message: message,
253
+ location: location, span: token&.span, expected: expected,
254
+ received: received, rendered: error.message)
255
+ end
256
+
257
+ # @rbs (String message) -> [Array[String], String?]
258
+ def expected_and_received(message)
259
+ match = message.match(/\Aexpected (.+), got (.+)\z/)
260
+ return [[], nil] unless match
261
+
262
+ expected = match[1]
263
+ received = match[2]
264
+ [expected ? [expected] : [], received]
265
+ end
266
+ end
267
+ end
268
+ end