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,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "symbol_labels"
4
+ require_relative "railroad_documentation"
5
+
6
+ module Ibex
7
+ module Codegen
8
+ # Renders normalized Grammar IR as a self-contained SVG railroad diagram.
9
+ module Railroad
10
+ CHAR_WIDTH = 8 #: Integer
11
+ BOX_HEIGHT = 28 #: Integer
12
+ BOX_PADDING = 20 #: Integer
13
+ TRACK_GAP = 18 #: Integer
14
+ ROW_HEIGHT = 46 #: Integer
15
+ SECTION_HEADER = 30 #: Integer
16
+ SECTION_GAP = 18 #: Integer
17
+ PAGE_PADDING = 20 #: Integer
18
+ TITLE_HEIGHT = 48 #: Integer
19
+ EPSILON_WIDTH = 34 #: Integer
20
+ START_RADIUS = 4 #: Integer
21
+ END_RADIUS = 6 #: Integer
22
+ MIN_RAIL_X = 180 #: Integer
23
+ # @rbs STYLE: String
24
+ STYLE = <<~CSS
25
+ text { fill: #172033; font: 14px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
26
+ .diagram-title { font-size: 18px; font-weight: 700; }
27
+ .rule-name { font-weight: 700; }
28
+ .rule-documentation { fill: #4b5565; font: 12px system-ui, sans-serif; }
29
+ .production-id { fill: #697386; font-size: 11px; text-anchor: end; }
30
+ .track { fill: none; stroke: #697386; stroke-width: 2; }
31
+ .start { fill: #172033; }
32
+ .end { fill: #fff; stroke: #172033; stroke-width: 2; }
33
+ .end-dot { fill: #172033; }
34
+ .symbol { stroke: #172033; stroke-width: 1.5; }
35
+ .terminal { fill: #fff3df; }
36
+ .nonterminal { fill: #e8f1ff; }
37
+ .symbol-label { text-anchor: middle; }
38
+ .epsilon { fill: #697386; font-style: italic; text-anchor: middle; }
39
+ CSS
40
+
41
+ extend RailroadDocumentation
42
+
43
+ class << self
44
+ # @rbs (IR::Grammar grammar) -> String
45
+ def render(grammar)
46
+ labels = SymbolLabels.build(grammar)
47
+ groups = production_groups(grammar)
48
+ rail_x = [MIN_RAIL_X, groups.map { |symbol, _| label_width(labels.fetch(symbol.id)) }.max.to_i + 44].max
49
+ width = document_width(grammar, groups, labels, rail_x)
50
+ height = document_height(groups)
51
+ lines = document_start(grammar, width, height)
52
+ append_sections(lines, grammar, groups, labels, rail_x)
53
+ lines << "</svg>"
54
+ "#{lines.join("\n")}\n"
55
+ end
56
+
57
+ private
58
+
59
+ # @rbs (IR::Grammar grammar) -> Array[[IR::GrammarSymbol, Array[IR::Production]]]
60
+ def production_groups(grammar)
61
+ grouped = grammar.productions.group_by(&:lhs)
62
+ grammar.nonterminals.map { |symbol| [symbol, grouped.fetch(symbol.id, [])] }
63
+ end
64
+
65
+ # @rbs (IR::Grammar grammar, Array[[IR::GrammarSymbol, Array[IR::Production]]] groups,
66
+ # Hash[Integer, String] labels, Integer rail_x) -> Integer
67
+ def document_width(grammar, groups, labels, rail_x)
68
+ rows = groups.flat_map(&:last)
69
+ widest = rows.map { |production| row_end_x(grammar, production, labels, rail_x) }.max
70
+ documentation_width = groups.flat_map { |symbol, _| documentation_lines(symbol.documentation) }
71
+ .map { |line| label_width(line) + (PAGE_PADDING * 2) }.max
72
+ [widest.to_i + PAGE_PADDING, documentation_width.to_i,
73
+ label_width("#{grammar.class_name} grammar") + (PAGE_PADDING * 2), 360].max
74
+ end
75
+
76
+ # @rbs (Array[[IR::GrammarSymbol, Array[IR::Production]]] groups) -> Integer
77
+ def document_height(groups)
78
+ sections = groups.sum do |symbol, productions|
79
+ section_header_height(symbol) + ([productions.length, 1].max * ROW_HEIGHT)
80
+ end
81
+ TITLE_HEIGHT + sections + (SECTION_GAP * [groups.length - 1, 0].max) + PAGE_PADDING
82
+ end
83
+
84
+ # @rbs (IR::Grammar grammar, Integer width, Integer height) -> Array[String]
85
+ def document_start(grammar, width, height)
86
+ title = escape(grammar.class_name)
87
+ [
88
+ %(<?xml version="1.0" encoding="UTF-8"?>),
89
+ %(<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{width} #{height}" width="#{width}" ),
90
+ %( height="#{height}" role="img" aria-labelledby="railroad-title">),
91
+ " <title id=\"railroad-title\">Railroad diagram for #{title}</title>",
92
+ " <style>",
93
+ STYLE.lines.map { |line| " #{line.rstrip}" }.join("\n"),
94
+ " </style>",
95
+ " <text class=\"diagram-title\" x=\"#{PAGE_PADDING}\" y=\"30\">#{title} grammar</text>"
96
+ ]
97
+ end
98
+
99
+ # @rbs (Array[String] lines, IR::Grammar grammar,
100
+ # Array[[IR::GrammarSymbol, Array[IR::Production]]] groups,
101
+ # Hash[Integer, String] labels, Integer rail_x) -> void
102
+ def append_sections(lines, grammar, groups, labels, rail_x)
103
+ y = TITLE_HEIGHT
104
+ groups.each do |symbol, productions|
105
+ lines << %( <g id="nonterminal-#{escape(symbol.id)}" class="rule" transform="translate(0 #{y})">)
106
+ lines << %( <text class="rule-name" x="#{PAGE_PADDING}" y="20">#{escape(labels.fetch(symbol.id))}</text>)
107
+ append_rule_documentation(lines, symbol)
108
+ header_height = section_header_height(symbol)
109
+ append_productions(lines, grammar, productions, labels, rail_x, header_height)
110
+ lines << " </g>"
111
+ y += header_height + ([productions.length, 1].max * ROW_HEIGHT) + SECTION_GAP
112
+ end
113
+ end
114
+
115
+ # @rbs (Array[String] lines, IR::Grammar grammar, Array[IR::Production] productions,
116
+ # Hash[Integer, String] labels, Integer rail_x, Integer header_height) -> void
117
+ def append_productions(lines, grammar, productions, labels, rail_x, header_height)
118
+ if productions.empty?
119
+ lines << %( <text class="epsilon" x="#{rail_x}" y="#{header_height + 18}">∅</text>)
120
+ return
121
+ end
122
+ productions.each_with_index do |production, index|
123
+ y = header_height + (index * ROW_HEIGHT) + (BOX_HEIGHT / 2)
124
+ lines.concat(production_lines(grammar, production, labels, rail_x, y))
125
+ end
126
+ end
127
+
128
+ # @rbs (IR::Grammar grammar, IR::Production production, Hash[Integer, String] labels,
129
+ # Integer rail_x, Integer offset_y) -> Array[String]
130
+ def production_lines(grammar, production, labels, rail_x, offset_y)
131
+ lines = [
132
+ %(<g class="production" data-production="#{escape(production.id)}" transform="translate(0 #{offset_y})">)
133
+ ]
134
+ lines << %(<text class="production-id" x="#{rail_x - 14}" y="4">p#{escape(production.id)}</text>)
135
+ lines << %(<circle class="start" cx="#{rail_x}" cy="0" r="#{START_RADIUS}"/>)
136
+ cursor = rail_x + START_RADIUS
137
+ lines << track(cursor, cursor + TRACK_GAP)
138
+ cursor += TRACK_GAP
139
+ cursor = append_rhs(lines, grammar, production, labels, cursor)
140
+ end_x = cursor + TRACK_GAP + END_RADIUS
141
+ lines << track(cursor, end_x - END_RADIUS)
142
+ lines << %(<circle class="end" cx="#{end_x}" cy="0" r="#{END_RADIUS}"/>)
143
+ lines << %(<circle class="end-dot" cx="#{end_x}" cy="0" r="2"/>)
144
+ lines << "</g>"
145
+ lines.map { |line| " #{line}" }
146
+ end
147
+
148
+ # @rbs (Array[String] lines, IR::Grammar grammar, IR::Production production,
149
+ # Hash[Integer, String] labels, Integer cursor) -> Integer
150
+ def append_rhs(lines, grammar, production, labels, cursor)
151
+ return append_epsilon(lines, cursor) if production.rhs.empty?
152
+
153
+ production.rhs.each_with_index do |symbol_id, index|
154
+ symbol = grammar.symbol_by_id(symbol_id) || raise(Ibex::Error, "missing grammar symbol id #{symbol_id}")
155
+ width = label_width(labels.fetch(symbol_id))
156
+ kind = symbol.terminal? ? "terminal" : "nonterminal"
157
+ lines << "<rect class=\"symbol #{kind}\" x=\"#{cursor}\" y=\"#{-(BOX_HEIGHT / 2)}\" " \
158
+ "width=\"#{width}\" height=\"#{BOX_HEIGHT}\" rx=\"5\"/>"
159
+ lines << "<text class=\"symbol-label\" x=\"#{cursor + (width / 2)}\" y=\"5\">" \
160
+ "#{escape(labels.fetch(symbol_id))}</text>"
161
+ cursor += width
162
+ next if index == production.rhs.length - 1
163
+
164
+ lines << track(cursor, cursor + TRACK_GAP)
165
+ cursor += TRACK_GAP
166
+ end
167
+ cursor
168
+ end
169
+
170
+ # @rbs (Array[String] lines, Integer cursor) -> Integer
171
+ def append_epsilon(lines, cursor)
172
+ lines << track(cursor, cursor + EPSILON_WIDTH)
173
+ lines << %(<text class="epsilon" x="#{cursor + (EPSILON_WIDTH / 2)}" y="-6">ε</text>)
174
+ cursor + EPSILON_WIDTH
175
+ end
176
+
177
+ # @rbs (IR::Grammar grammar, IR::Production production, Hash[Integer, String] labels,
178
+ # Integer rail_x) -> Integer
179
+ def row_end_x(grammar, production, labels, rail_x)
180
+ widths = production.rhs.map do |symbol_id|
181
+ raise Ibex::Error, "missing grammar symbol id #{symbol_id}" unless grammar.symbol_by_id(symbol_id)
182
+
183
+ label_width(labels.fetch(symbol_id))
184
+ end
185
+ content = widths.empty? ? EPSILON_WIDTH : widths.sum + (TRACK_GAP * [widths.length - 1, 0].max)
186
+ rail_x + START_RADIUS + TRACK_GAP + content + TRACK_GAP + (END_RADIUS * 2)
187
+ end
188
+
189
+ # @rbs (String value) -> Integer
190
+ def label_width(value)
191
+ glyph_width = value.each_char.sum { |character| character.ascii_only? ? CHAR_WIDTH : CHAR_WIDTH * 2 }
192
+ [glyph_width + BOX_PADDING, 38].max
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Codegen
5
+ # Documentation layout helpers mixed into the railroad renderer singleton.
6
+ module RailroadDocumentation
7
+ DOCUMENTATION_COLUMNS = 72 #: Integer
8
+ DOCUMENTATION_LINE_HEIGHT = 18 #: Integer
9
+ DOCUMENTATION_GAP = 8 #: Integer
10
+
11
+ private
12
+
13
+ # @rbs (Array[String] lines, IR::GrammarSymbol symbol) -> void
14
+ def append_rule_documentation(lines, symbol)
15
+ # @type self: singleton(Railroad)
16
+ documentation = symbol.documentation
17
+ return unless documentation
18
+
19
+ lines << " <desc>#{escape(documentation)}</desc>"
20
+ documentation_lines(documentation).each_with_index do |line, index|
21
+ y = Railroad::SECTION_HEADER + DOCUMENTATION_GAP + (index * DOCUMENTATION_LINE_HEIGHT)
22
+ lines << %( <text class="rule-documentation" x="#{Railroad::PAGE_PADDING}" y="#{y}">#{escape(line)}</text>)
23
+ end
24
+ end
25
+
26
+ # @rbs (IR::GrammarSymbol symbol) -> Integer
27
+ def section_header_height(symbol)
28
+ # @type self: singleton(Railroad)
29
+ lines = documentation_lines(symbol.documentation)
30
+ return Railroad::SECTION_HEADER if lines.empty?
31
+
32
+ Railroad::SECTION_HEADER + DOCUMENTATION_GAP + (lines.length * DOCUMENTATION_LINE_HEIGHT)
33
+ end
34
+
35
+ # @rbs (String? documentation) -> Array[String]
36
+ def documentation_lines(documentation)
37
+ return [] unless documentation
38
+
39
+ documentation.split("\n", -1).flat_map do |line|
40
+ characters = line.each_char.to_a
41
+ characters.empty? ? [""] : characters.each_slice(DOCUMENTATION_COLUMNS).map(&:join)
42
+ end
43
+ end
44
+
45
+ # @rbs (String | Integer value) -> String
46
+ def escape(value)
47
+ xml = value.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
48
+ .gsub(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u, "\uFFFD")
49
+ xml.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
50
+ .gsub('"', "&quot;").gsub("'", "&apos;")
51
+ end
52
+
53
+ # @rbs (Integer from, Integer to) -> String
54
+ def track(from, to)
55
+ %(<line class="track" x1="#{from}" y1="0" x2="#{to}" y2="0"/>)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,18 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "generated_action_abi"
4
+ require_relative "cst_metadata"
5
+
3
6
  module Ibex
4
7
  module Codegen
5
8
  # Generates an RBS declaration for the public surface of a generated parser.
9
+ # rubocop:disable Metrics/ClassLength -- one generator owns the complete parser signature.
6
10
  class RBS
7
11
  # @rbs @automaton: IR::Automaton
8
12
  # @rbs @grammar: IR::Grammar
9
13
  # @rbs @superclass: String
14
+ # @rbs @omit_action_call: bool
15
+ # @rbs @generated_action_abi: GeneratedActionABI::Cache
10
16
 
11
- # @rbs (IR::Automaton automaton, ?superclass: String?) -> void
12
- def initialize(automaton, superclass: nil)
17
+ # @rbs (IR::Automaton automaton, ?superclass: String?, ?omit_action_call: bool?) -> void
18
+ def initialize(automaton, superclass: nil, omit_action_call: nil)
13
19
  @automaton = automaton
14
20
  @grammar = automaton.grammar
15
21
  @superclass = superclass || @grammar.superclass || "Ibex::Runtime::Parser"
22
+ @omit_action_call = omit_action_call.nil? ? @grammar.options[:omit_action_call] : omit_action_call
23
+ @generated_action_abi = GeneratedActionABI::Cache.new
16
24
  end
17
25
 
18
26
  # @rbs () -> String
@@ -21,7 +29,12 @@ module Ibex
21
29
  modules, class_name = class_parts
22
30
  modules.each { |name| lines << "module #{name}" }
23
31
  lines << "class #{class_name} < #{@superclass}"
32
+ append_ast_contract(lines)
33
+ append_syntax_contract(lines)
24
34
  append_contract(lines)
35
+ append_lexer_contract(lines)
36
+ append_value_printer_signatures(lines)
37
+ append_actions(lines)
25
38
  lines << "end"
26
39
  modules.reverse_each { lines << "end" }
27
40
  "#{lines.join("\n")}\n"
@@ -29,14 +42,321 @@ module Ibex
29
42
 
30
43
  private
31
44
 
45
+ # @rbs (Array[String] lines) -> void
46
+ def append_ast_contract(lines)
47
+ definitions = ast_node_definitions
48
+ return if definitions.empty?
49
+
50
+ lines << " module AST"
51
+ definitions.each_value { |node| append_ast_node_contract(lines, node) }
52
+ append_visitor_contract(lines, definitions)
53
+ append_listener_contract(lines, definitions)
54
+ lines.push(" end", "")
55
+ end
56
+
57
+ # @rbs (Array[String] lines) -> void
58
+ def append_syntax_contract(lines)
59
+ definitions = syntax_node_definitions
60
+ return if definitions.empty? || @grammar.options[:cst] != true
61
+
62
+ lines << " module Syntax"
63
+ definitions.each_value { |definition| append_syntax_node_contract(lines, definition) }
64
+ lines.push(" end", "")
65
+ end
66
+
67
+ # @rbs () -> Hash[String, Hash[Symbol, untyped]]
68
+ def syntax_node_definitions
69
+ metadata = CSTMetadata.new(@grammar).build
70
+ slots = metadata.fetch(:slots)
71
+ definitions = {} #: Hash[String, Hash[Symbol, untyped]]
72
+ @grammar.productions.each do |production|
73
+ node = production.node
74
+ next unless node
75
+
76
+ slot = slots.fetch(production.id)
77
+ name = node.fetch(:name)
78
+ previous = definitions[name]
79
+ fields = previous ? merge_syntax_fields(previous.fetch(:fields), slot.fetch(:fields)) : slot.fetch(:fields)
80
+ definitions[name] = { name: name, kind: slot.fetch(:node_kind), fields: fields }.freeze
81
+ end
82
+ definitions
83
+ end
84
+
85
+ # @rbs (Hash[String, untyped] left, Hash[String, untyped] right) -> Hash[String, untyped]
86
+ def merge_syntax_fields(left, right)
87
+ left.to_h do |name, left_slot|
88
+ right_slot = right.fetch(name)
89
+ left_index = left_slot.is_a?(Hash) ? left_slot.fetch(:index) : left_slot
90
+ right_index = right_slot.is_a?(Hash) ? right_slot.fetch(:index) : right_slot
91
+ raise Ibex::Error, "inconsistent CST slot for #{name}" unless left_index == right_index
92
+
93
+ [name, left_slot == right_slot ? left_slot : left_index]
94
+ end.freeze
95
+ end
96
+
97
+ # @rbs (Array[String] lines, Hash[Symbol, untyped] definition) -> void
98
+ def append_syntax_node_contract(lines, definition)
99
+ name = definition.fetch(:name)
100
+ fields = definition.fetch(:fields)
101
+ lines.push(
102
+ " class #{name} < Ibex::Runtime::CST::TypedNode",
103
+ " KIND: Integer",
104
+ " def self.cast: (Ibex::Runtime::CST::SyntaxNode node) -> #{name}?"
105
+ )
106
+ fields.each_with_index do |(field, _slot), index|
107
+ lines << " def #{field}: () -> #{syntax_field_type(name, index)}"
108
+ end
109
+ append_repetition_contracts(lines, fields)
110
+ lines << " end"
111
+ end
112
+
113
+ # @rbs (String node_name, Integer index) -> String
114
+ def syntax_field_type(node_name, index)
115
+ symbol_ids = @grammar.productions.filter_map do |production|
116
+ production.rhs.fetch(index) if production.node&.fetch(:name) == node_name
117
+ end
118
+ types = symbol_ids.map do |symbol_id|
119
+ symbol = @grammar.symbols.fetch(symbol_id)
120
+ symbol.terminal? ? "Ibex::Runtime::CST::SyntaxToken" : "Ibex::Runtime::CST::SyntaxNode"
121
+ end.uniq
122
+ types.one? ? types.fetch(0) : "(#{types.join(' | ')})"
123
+ end
124
+
125
+ # @rbs (Array[String] lines, Hash[String, untyped] fields) -> void
126
+ def append_repetition_contracts(lines, fields)
127
+ repeated = fields.select { |_field, slot| slot.is_a?(Hash) && slot[:extraction] }
128
+ element = "Enumerator[Ibex::Runtime::CST::SyntaxNode | Ibex::Runtime::CST::SyntaxToken, void]"
129
+ repeated.each do |field, slot|
130
+ lines << " def each_#{field}_element: () -> #{element}"
131
+ lines << " def each_#{field}_separator: () -> #{element}" if slot.fetch(:extraction) == :separated_list
132
+ end
133
+ return unless repeated.one?
134
+
135
+ _field, slot = repeated.first
136
+ lines << " def each_element: () -> #{element}"
137
+ lines << " def each_separator: () -> #{element}" if slot.fetch(:extraction) == :separated_list
138
+ end
139
+
140
+ # @rbs () -> Hash[String, IR::node_annotation]
141
+ def ast_node_definitions
142
+ @grammar.productions.filter_map(&:node).to_h { |node| [node.fetch(:name), node] }
143
+ end
144
+
145
+ # @rbs (Array[String] lines, IR::node_annotation node) -> void
146
+ def append_ast_node_contract(lines, node)
147
+ name = node.fetch(:name)
148
+ fields = node.fetch(:fields)
149
+ types = ast_node_field_types(name, fields.length)
150
+ lines << " class #{name} < Data"
151
+ fields.each_with_index { |field, index| lines << " attr_reader #{field}: #{types.fetch(index)}" }
152
+ keywords = fields.each_with_index.map { |field, index| "#{field}: #{types.fetch(index)}" }
153
+ lines << " def self.new: (#{keywords.join(', ')}) -> instance"
154
+ lines << " def initialize: (#{keywords.join(', ')}) -> void"
155
+ lines << " end"
156
+ end
157
+
158
+ # @rbs (String name, Integer length) -> Array[String]
159
+ def ast_node_field_types(name, length)
160
+ productions = @grammar.productions.select { |production| production.node&.fetch(:name) == name }
161
+ Array.new(length) do |index|
162
+ types = productions.map { |production| semantic_type(production.rhs.fetch(index)) }.uniq
163
+ types.length == 1 ? types.fetch(0) : "(#{types.join(' | ')})"
164
+ end
165
+ end
166
+
167
+ # @rbs (Array[String] lines, Hash[String, IR::node_annotation] definitions) -> void
168
+ def append_visitor_contract(lines, definitions)
169
+ lines.push(" class Visitor", " def visit: (untyped node) -> untyped")
170
+ definitions.each_value do |node|
171
+ name = node.fetch(:name)
172
+ lines << " def visit_#{ast_method_name(name)}: (#{name} node) -> untyped"
173
+ end
174
+ lines.push(" def visit_children: (untyped node) -> untyped", " end")
175
+ end
176
+
177
+ # @rbs (Array[String] lines, Hash[String, IR::node_annotation] definitions) -> void
178
+ def append_listener_contract(lines, definitions)
179
+ lines.push(" class Listener", " def walk: (untyped node) -> untyped",
180
+ " def enter: (untyped node) -> void", " def exit: (untyped node) -> void")
181
+ definitions.each_value do |node|
182
+ name = node.fetch(:name)
183
+ method = ast_method_name(name)
184
+ lines << " def enter_#{method}: (#{name} node) -> void"
185
+ lines << " def exit_#{method}: (#{name} node) -> void"
186
+ end
187
+ lines.push(" def listener_children: (untyped node) -> Array[untyped]", " end")
188
+ end
189
+
190
+ # @rbs (String name) -> String
191
+ def ast_method_name(name)
192
+ name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
193
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
194
+ .downcase
195
+ end
196
+
32
197
  # @rbs (Array[String] lines) -> void
33
198
  def append_contract(lines)
34
199
  lines.push(" PARSER_TABLE_FORMAT_VERSION: Integer",
200
+ " GRAMMAR_DIGEST: String", " STATE_COUNT: Integer", " PRODUCTION_COUNT: Integer",
35
201
  " TOKEN_IDS: Hash[untyped, Integer]", " TOKEN_NAMES: Hash[Integer, String]",
36
202
  " ACTIONS: untyped", " GOTOS: untyped", " DEFAULT_ACTIONS: Array[untyped]",
203
+ " EAGER_REDUCTIONS: Hash[Integer, [:reduce, Integer]]",
37
204
  " PRODUCTIONS: Array[Hash[Symbol, untyped]]",
205
+ " ERROR_MESSAGES: Hash[Integer, String | { id: String, message: String }]",
38
206
  " PARSER_TABLES: Hash[Symbol, untyped]", " DEBUG_ENABLED: bool", "",
39
207
  " def self.parser_tables: () -> Hash[Symbol, untyped]")
208
+ lines << " SYMBOL_NAMES: Hash[Integer, String]" if @grammar.options[:cst] == true
209
+ lines << " CST_METADATA: Hash[Symbol, untyped]" if @grammar.options[:cst] == true
210
+ lines << " CST_KIND_MODEL: Ibex::Runtime::CST::Kind" if @grammar.options[:cst] == true
211
+ append_entry_contract(lines)
212
+ append_parameter_contract(lines)
213
+ end
214
+
215
+ # @rbs (Array[String] lines) -> void
216
+ def append_entry_contract(lines)
217
+ return unless @grammar.starts.length > 1
218
+
219
+ lines << " ENTRY_STATES: Hash[Symbol, Integer]"
220
+ @grammar.starts.each { |name| lines << " def parse_#{name}: () -> untyped" }
221
+ end
222
+
223
+ # @rbs (Array[String] lines) -> void
224
+ def append_parameter_contract(lines)
225
+ parameters = @grammar.parser_parameters
226
+ return if parameters.empty?
227
+
228
+ parameters.each do |parameter|
229
+ lines << " @#{parameter[:name]}: #{parameter[:semantic_type] || 'untyped'}"
230
+ end
231
+ keywords = parameters.map do |parameter|
232
+ "#{parameter[:name]}: #{parameter[:semantic_type] || 'untyped'}"
233
+ end
234
+ lines << ""
235
+ lines << " def initialize: (#{keywords.join(', ')}, **untyped) -> void"
236
+ end
237
+
238
+ # @rbs (Array[String] lines) -> void
239
+ def append_lexer_contract(lines)
240
+ lexer = @grammar.lexer
241
+ return unless lexer
242
+
243
+ lines.push(
244
+ " LEXER_STATES: Array[String]",
245
+ " LEXER_RULES_BY_STATE: Hash[Symbol, Array[Hash[Symbol, untyped]]]",
246
+ "",
247
+ " def lex: (String | IO | Fiber source, ?file: String) -> self",
248
+ " def parse: (String | IO | Fiber source, ?file: String) -> untyped",
249
+ " def lexer_state: () -> Symbol",
250
+ " def lexer_state=: (Symbol | String state) -> Symbol",
251
+ " def next_token: () -> [untyped, untyped, Hash[Symbol, untyped]]"
252
+ )
253
+ if @grammar.options[:cst] == true
254
+ lines << " def parse_with_syntax: (String | IO | Fiber source, ?file: String) -> " \
255
+ "Ibex::Runtime::CST::ParseResult"
256
+ end
257
+ lexer.rules.each do |rule|
258
+ lines << " private def _ibex_lexer_action_#{rule.id}: (String lexeme) -> untyped" if rule.action
259
+ end
260
+ end
261
+
262
+ # @rbs (Array[String] lines) -> void
263
+ def append_value_printer_signatures(lines)
264
+ @grammar.value_printers.each do |printer|
265
+ symbol = @grammar.symbol(printer[:symbol]) || raise(Ibex::Error, "missing printer symbol #{printer[:symbol]}")
266
+ lines << ""
267
+ lines << " private def _ibex_value_printer_#{symbol.id}: " \
268
+ "(#{symbol.semantic_type || 'untyped'} value) -> untyped"
269
+ end
270
+ end
271
+
272
+ # @rbs (Array[String] lines) -> void
273
+ def append_actions(lines)
274
+ actions = @grammar.productions.select { |production| action_method?(production) }
275
+ return if actions.empty?
276
+
277
+ lines << ""
278
+ actions.each do |production|
279
+ append_action_signature(lines, production)
280
+ append_composed_fragment_signatures(lines, production) if composed_action?(production)
281
+ end
282
+ end
283
+
284
+ # @rbs (Array[String] lines, IR::Production production) -> void
285
+ def append_action_signature(lines, production)
286
+ parameters = production.rhs.map { |symbol_id| semantic_type(symbol_id) }.join(", ")
287
+ result = production.node ? "AST::#{production.node.fetch(:name)}" : semantic_type(production.lhs)
288
+ if @generated_action_abi.positional_values?(production)
289
+ lines << " private def _ibex_action_#{production.id}: (#{parameters}) -> #{result}"
290
+ return
291
+ end
292
+ if @generated_action_abi.values_only?(production)
293
+ lines << " private def _ibex_action_#{production.id}: ([#{parameters}]) -> #{result}"
294
+ return
295
+ end
296
+
297
+ locations = Array.new(production.rhs.length, "untyped").join(", ")
298
+ lookahead = composed_action?(production) ? ", untyped" : ""
299
+ lines << " private def _ibex_action_#{production.id}: " \
300
+ "([#{parameters}], Array[untyped], [#{locations}], Array[untyped], " \
301
+ "Ibex::Runtime::LocationSpan?#{lookahead}) -> #{result}"
302
+ end
303
+
304
+ # @rbs (Array[String] lines, IR::Production production) -> void
305
+ def append_composed_fragment_signatures(lines, production)
306
+ plan = production.action&.composition&.dig(:plan)
307
+ raise Ibex::Error, "missing action composition plan" unless plan
308
+
309
+ plan.fetch(:steps).each_with_index do |step, index|
310
+ next unless step[:code]
311
+
312
+ inputs = step.fetch(:inputs).map do |slot|
313
+ composition_slot_type(production, plan, slot, index)
314
+ end
315
+ locations = Array.new(inputs.length, "untyped").join(", ")
316
+ result = step[:result_type] || "untyped"
317
+ lines << " private def _ibex_inline_fragment_#{production.id}_#{index}: " \
318
+ "([#{inputs.join(', ')}], Array[untyped], [#{locations}], Array[untyped], " \
319
+ "Ibex::Runtime::LocationSpan?) -> #{result}"
320
+ end
321
+ end
322
+
323
+ # @rbs (IR::Production production, Hash[Symbol, untyped] plan, Integer slot, Integer step_index) -> String
324
+ def composition_slot_type(production, plan, slot, step_index)
325
+ physical = plan.fetch(:physical)
326
+ return semantic_type(production.rhs.fetch(slot)) if slot < physical
327
+
328
+ prior_index = slot - physical
329
+ raise Ibex::Error, "invalid composed action slot #{slot}" if prior_index >= step_index
330
+
331
+ plan.fetch(:steps).fetch(prior_index)[:result_type] || "untyped"
332
+ end
333
+
334
+ # @rbs (IR::Production production) -> bool
335
+ def action_method?(production)
336
+ !!(production.node || production.action || !@omit_action_call)
337
+ end
338
+
339
+ # @rbs (IR::Production production) -> bool
340
+ def composed_action?(production)
341
+ production.action&.composition&.dig(:plan, :version) == 1
342
+ end
343
+
344
+ # @rbs (Integer symbol_id) -> String
345
+ def semantic_type(symbol_id)
346
+ symbol = @grammar.symbol_by_id(symbol_id) || raise(Ibex::Error, "missing grammar symbol id #{symbol_id}")
347
+ symbol.semantic_type || inferred_ast_type(symbol_id) || "untyped"
348
+ end
349
+
350
+ # @rbs (Integer symbol_id) -> String?
351
+ def inferred_ast_type(symbol_id)
352
+ productions = @grammar.productions.select { |production| production.lhs == symbol_id }
353
+ return if productions.empty? || productions.any? { |production| production.node.nil? }
354
+
355
+ names = productions.map { |production| production.node&.fetch(:name) }.compact.uniq
356
+ return if names.empty?
357
+
358
+ types = names.map { |name| "AST::#{name}" }
359
+ types.length == 1 ? types.fetch(0) : "(#{types.join(' | ')})"
40
360
  end
41
361
 
42
362
  # @rbs () -> [Array[String], String]
@@ -50,5 +370,6 @@ module Ibex
50
370
  @automaton.grammar_digest.delete_prefix("sha256:").chars.first(12).join
51
371
  end
52
372
  end
373
+ # rubocop:enable Metrics/ClassLength
53
374
  end
54
375
  end