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
@@ -4,19 +4,197 @@ module Ibex
4
4
  module Frontend
5
5
  # Public grammar parser backed by Ibex's generated LR frontend.
6
6
  class Parser
7
+ DEFAULT_MAX_DIAGNOSTICS = 20 #: Integer
8
+ STRICT_LEXICAL_DIAGNOSTIC_LIMIT = 1 #: Integer
9
+ private_constant :STRICT_LEXICAL_DIAGNOSTIC_LIMIT
10
+
7
11
  attr_reader :implementation #: GeneratedParser
8
12
 
13
+ # @rbs @source_document: SourceDocument?
14
+ # @rbs @parsed_document: SourceDocument?
15
+ # @rbs @parsed_node: AST::Root | AST::Fragment | nil
16
+ # @rbs @parse_error_message: String?
17
+ # @rbs @source_document_error_message: String?
18
+ # @rbs @tokens: Array[Token]
19
+ # @rbs @mode: Symbol
20
+ # @rbs @lexical_diagnostics: Array[Diagnostic]
21
+
9
22
  # @rbs (String | Array[Token] source, ?file: String, ?mode: Symbol) -> void
10
- def initialize(source, file: "(grammar)", mode: :racc)
11
- raise ArgumentError, "mode must be :racc or :extended" unless %i[racc extended].include?(mode)
23
+ def initialize(source, file: "(grammar)", mode: :default)
24
+ raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode)
12
25
 
13
- tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
26
+ @mode = mode
27
+ @lexical_diagnostics = [] #: Array[Diagnostic]
28
+ @parse_error_message = nil
29
+ @source_document_error_message = nil
30
+ tokens = source.is_a?(Array) ? source : tokenize_source(source, file)
31
+ @tokens = tokens
14
32
  @implementation = GeneratedParser.new(tokens, mode: mode)
15
33
  end
16
34
 
17
35
  # @rbs () -> AST::Root
18
36
  def parse
19
- @implementation.parse
37
+ node = parse_node
38
+ return node if node.is_a?(AST::Root)
39
+
40
+ raise Ibex::Error, "#{node.loc}: fragment input requires Parser#parse_fragment"
41
+ end
42
+
43
+ # Parse an explicit fragment without resolving its includes.
44
+ # @rbs () -> AST::Fragment
45
+ def parse_fragment
46
+ node = parse_node
47
+ return node if node.is_a?(AST::Fragment)
48
+
49
+ raise Ibex::Error, "#{node.loc}: root grammar input cannot be parsed as a fragment"
50
+ end
51
+
52
+ # Parse the grammar and return its lossless source model.
53
+ # @rbs () -> SourceDocument
54
+ def parse_document
55
+ parsed_document = @parsed_document
56
+ return parsed_document if parsed_document && parsed_document.ast.is_a?(AST::Root)
57
+
58
+ @parsed_document = source_document!.with_ast(parse)
59
+ end
60
+
61
+ # Parse either a root grammar or an explicit fragment and return its lossless source model.
62
+ # @rbs () -> SourceDocument
63
+ def parse_source_document
64
+ parsed_document = @parsed_document
65
+ return parsed_document if parsed_document
66
+
67
+ @parsed_document = source_document!.with_ast(parse_node)
68
+ end
69
+
70
+ # Parse with conservative boundary recovery and collect multiple errors.
71
+ # @rbs (?max_diagnostics: Integer) -> ParseResult
72
+ def parse_with_diagnostics(max_diagnostics: DEFAULT_MAX_DIAGNOSTICS)
73
+ unless max_diagnostics.is_a?(Integer) && max_diagnostics.positive?
74
+ raise ArgumentError, "max_diagnostics must be a positive integer"
75
+ end
76
+
77
+ lexical_diagnostics = lexical_diagnostics_for(max_diagnostics)
78
+ if @source_document.nil? && @lexical_diagnostics.any?
79
+ return ParseResult.new(diagnostics: lexical_diagnostics, ast: nil, document: nil)
80
+ end
81
+
82
+ recovery = DiagnosticRecovery.new(
83
+ @tokens, mode: @mode, max_diagnostics: max_diagnostics
84
+ )
85
+ node, syntax_diagnostics = recovery.parse
86
+ node = enrich_rule_documentation(node) if node
87
+ ast, syntax_diagnostics = root_diagnostic_result(node, syntax_diagnostics)
88
+ diagnostics = merge_diagnostics(lexical_diagnostics, syntax_diagnostics, max_diagnostics)
89
+ document = if diagnostics.empty? && ast
90
+ @source_document&.with_ast(ast)
91
+ else
92
+ @source_document
93
+ end
94
+ ParseResult.new(diagnostics: diagnostics, ast: ast, document: document)
95
+ end
96
+
97
+ private
98
+
99
+ # @rbs () -> SourceDocument
100
+ def source_document!
101
+ source_document_error_message = @source_document_error_message
102
+ raise Ibex::Error, source_document_error_message if source_document_error_message
103
+
104
+ source_document = @source_document
105
+ raise ArgumentError, "source document parsing requires String source" unless source_document
106
+
107
+ source_document
108
+ end
109
+
110
+ # @rbs () -> (AST::Root | AST::Fragment)
111
+ def parse_node
112
+ lexical = @lexical_diagnostics.first
113
+ raise Ibex::Error, lexical.to_s if lexical
114
+
115
+ parse_error_message = @parse_error_message
116
+ raise Ibex::Error, parse_error_message if parse_error_message
117
+
118
+ node = @parsed_node
119
+ return node if node
120
+
121
+ begin
122
+ @parsed_node = enrich_rule_documentation(@implementation.parse)
123
+ rescue Ibex::Error => e
124
+ @parse_error_message = e.message.dup.freeze
125
+ raise
126
+ end
127
+ end
128
+
129
+ # @rbs (AST::Root | AST::Fragment node) -> (AST::Root | AST::Fragment)
130
+ def enrich_rule_documentation(node)
131
+ source_document = @source_document
132
+ return node unless source_document
133
+
134
+ RuleDocumentation.enrich(node, source_document)
135
+ end
136
+
137
+ # @rbs (AST::Root | AST::Fragment | nil node, Array[Diagnostic] diagnostics) ->
138
+ # [AST::Root?, Array[Diagnostic]]
139
+ def root_diagnostic_result(node, diagnostics)
140
+ return [node, diagnostics] unless node.is_a?(AST::Fragment)
141
+
142
+ diagnostic = Diagnostic.new(
143
+ code: "frontend.syntax_error", phase: :syntax,
144
+ message: "fragment input requires Parser#parse_fragment", location: node.loc
145
+ )
146
+ [nil, diagnostics + [diagnostic]]
147
+ end
148
+
149
+ # @rbs (String source, String file) -> Array[Token]
150
+ def tokenize_source(source, file)
151
+ document, diagnostics = Lexer.new(source, file: file).tokenize_document_recovering(
152
+ max_diagnostics: STRICT_LEXICAL_DIAGNOSTIC_LIMIT
153
+ )
154
+ @source_document = document
155
+ @lexical_diagnostics = diagnostics
156
+ document.tokens
157
+ rescue Ibex::Error => e
158
+ location = Location.new(file: file, line: 1, column: 1)
159
+ @lexical_diagnostics = [
160
+ Diagnostic.new(code: "frontend.lexical_error", phase: :lexical, message: e.message,
161
+ location: location, rendered: e.message)
162
+ ]
163
+ @source_document_error_message = e.message.dup.freeze
164
+ [Token.new(type: :eof, value: nil, location: location, span: nil)]
165
+ end
166
+
167
+ # @rbs (Integer max_diagnostics) -> Array[Diagnostic]
168
+ def lexical_diagnostics_for(max_diagnostics)
169
+ return @lexical_diagnostics if @lexical_diagnostics.empty? || max_diagnostics == 1
170
+
171
+ source_document = @source_document
172
+ return @lexical_diagnostics.first(max_diagnostics) unless source_document
173
+
174
+ _, diagnostics = Lexer.new(source_document.source, file: source_document.file)
175
+ .tokenize_document_recovering(max_diagnostics: max_diagnostics)
176
+ diagnostics
177
+ end
178
+
179
+ # @rbs (Array[Diagnostic] lexical, Array[Diagnostic] syntax, Integer limit) -> Array[Diagnostic]
180
+ def merge_diagnostics(lexical, syntax, limit)
181
+ (lexical + syntax)
182
+ .uniq { |diagnostic| diagnostic_key(diagnostic) }
183
+ .sort_by { |diagnostic| diagnostic_sort_key(diagnostic) }
184
+ .first(limit)
185
+ .freeze
186
+ end
187
+
188
+ # @rbs (Diagnostic diagnostic) -> [String, String, Integer, Integer, String]
189
+ def diagnostic_key(diagnostic)
190
+ location = diagnostic.location
191
+ [diagnostic.phase.to_s, location.file, location.line, location.column, diagnostic.message]
192
+ end
193
+
194
+ # @rbs (Diagnostic diagnostic) -> [String, Integer, Integer, String, String]
195
+ def diagnostic_sort_key(diagnostic)
196
+ location = diagnostic.location
197
+ [location.file, location.line, location.column, diagnostic.phase.to_s, diagnostic.code]
20
198
  end
21
199
  end
22
200
  end
@@ -2,12 +2,15 @@
2
2
 
3
3
  require_relative "../error"
4
4
  require_relative "../tables"
5
+ require_relative "source_span"
5
6
  require_relative "source_cursor"
7
+ require_relative "source_document"
6
8
  require_relative "action_scanner"
7
9
  require_relative "lexer"
8
10
  require_relative "ast"
9
11
  require_relative "parser/declarations"
10
12
  require_relative "parser/rules"
13
+ require_relative "parser/parameters"
11
14
  require_relative "bootstrap_parser"
12
15
  require_relative "../ir"
13
16
  require_relative "../normalize"
@@ -20,6 +23,7 @@ module Ibex
20
23
  # Builds the committed frontend parser from its canonical Ibex grammar.
21
24
  module Regenerator
22
25
  GRAMMAR_PATH = File.expand_path("grammar.y", File.dirname(__FILE__)) #: String
26
+ SHADOW_GRAMMAR_PATH = File.expand_path("shadow_grammar.y", File.dirname(__FILE__)) #: String
23
27
 
24
28
  module_function
25
29
 
@@ -29,13 +33,34 @@ module Ibex
29
33
  ast = BootstrapParser.new(source, file: relative_grammar_path).parse
30
34
  grammar = Normalizer.new(ast).normalize
31
35
  automaton = LALR::Builder.new(grammar).build
32
- Codegen::Ruby.new(automaton, table: :compact, line_convert: false).generate
36
+ Codegen::Ruby.new(
37
+ automaton, table: :compact, line_convert: false, runtime_require: nil
38
+ ).generate
39
+ end
40
+
41
+ # Build the non-published parameterized/inline grammar used to verify
42
+ # that preview composition features can describe the production
43
+ # frontend without changing its observable AST.
44
+ # @rbs () -> String
45
+ def generate_shadow
46
+ source = File.read(SHADOW_GRAMMAR_PATH)
47
+ ast = BootstrapParser.new(source, file: relative_shadow_grammar_path, mode: :extended).parse
48
+ grammar = Normalizer.new(ast, mode: :extended).normalize
49
+ automaton = LALR::Builder.new(grammar).build
50
+ Codegen::Ruby.new(
51
+ automaton, table: :compact, line_convert: false, runtime_require: nil
52
+ ).generate
33
53
  end
34
54
 
35
55
  # @rbs () -> String
36
56
  def relative_grammar_path
37
57
  "lib/ibex/frontend/grammar.y"
38
58
  end
59
+
60
+ # @rbs () -> String
61
+ def relative_shadow_grammar_path
62
+ "lib/ibex/frontend/shadow_grammar.y"
63
+ end
39
64
  end
40
65
  end
41
66
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Immutable result of resolving one root grammar and its fragment closure.
6
+ class Resolution
7
+ EMPTY_CHAIN = Array.new(0).freeze #: Array[IR::source_provenance]
8
+ private_constant :EMPTY_CHAIN
9
+
10
+ attr_reader :root #: AST::Root
11
+ attr_reader :root_path #: String
12
+ attr_reader :root_directory #: String
13
+ attr_reader :files #: Array[String]
14
+
15
+ # @rbs (root: AST::Root, root_path: String, root_directory: String, files: Array[String],
16
+ # include_chains: Hash[AST::Rule, Array[IR::source_provenance]]) -> void
17
+ def initialize(root:, root_path:, root_directory:, files:, include_chains:)
18
+ @root = deep_freeze_ast(root)
19
+ @root_path = root_path.dup.freeze
20
+ @root_directory = root_directory.dup.freeze
21
+ @files = files.map { |file| file.dup.freeze }.freeze
22
+ chains = {} #: Hash[AST::Rule, Array[IR::source_provenance]]
23
+ @include_chains = chains.compare_by_identity
24
+ include_chains.each do |rule, chain|
25
+ frozen_chain = chain.map { |entry| copy_provenance(entry) }.freeze
26
+ @include_chains[rule] = frozen_chain
27
+ end
28
+ @include_chains.freeze
29
+ freeze
30
+ end
31
+
32
+ # @rbs (AST::Rule rule) -> Array[IR::source_provenance]
33
+ def include_chain_for(rule)
34
+ @include_chains.fetch(rule, EMPTY_CHAIN)
35
+ end
36
+
37
+ private
38
+
39
+ # @rbs (untyped value) -> untyped
40
+ def deep_freeze_ast(value)
41
+ case value
42
+ when Struct
43
+ value.each_pair { |_name, child| deep_freeze_ast(child) }
44
+ when Array
45
+ value.each { |child| deep_freeze_ast(child) }
46
+ when Hash
47
+ value.each do |key, child|
48
+ deep_freeze_ast(key)
49
+ deep_freeze_ast(child)
50
+ end
51
+ end
52
+ value.freeze
53
+ end
54
+
55
+ # @rbs (IR::source_provenance entry) -> IR::source_provenance
56
+ def copy_provenance(entry)
57
+ span = entry[:byte_span]
58
+ frozen_span = span && { start: span[:start], end: span[:end] } #: IR::byte_span?
59
+ frozen_span&.freeze
60
+ provenance = {
61
+ file: entry[:file]&.dup&.freeze,
62
+ root: entry[:root]&.dup&.freeze,
63
+ byte_span: frozen_span
64
+ } #: IR::source_provenance
65
+ provenance.freeze
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # An actual filesystem read failure while resolving a grammar closure.
6
+ class ResolutionIOError < Ibex::Error; end
7
+
8
+ # Loads an explicit root grammar and resolves its extended-mode fragment graph.
9
+ class Resolver
10
+ GLOB_CHARACTERS = /[*?\[\]{}]/ #: Regexp
11
+ WINDOWS_ABSOLUTE = %r{\A(?:[A-Za-z]:[\\/]|\\\\)} #: Regexp
12
+
13
+ # @rbs @input_path: String
14
+ # @rbs @mode: Symbol
15
+ # @rbs @loader: SourceLoader
16
+ # @rbs @root_path: String
17
+ # @rbs @root_directory: String
18
+ # @rbs @files: Array[String]
19
+ # @rbs @visiting: Array[String]
20
+ # @rbs @loaded: Hash[String, bool]
21
+ # @rbs @include_chains: Hash[AST::Rule, Array[IR::source_provenance]]
22
+ # @rbs @attempted_paths: Array[String]
23
+
24
+ attr_reader :attempted_paths #: Array[String]
25
+
26
+ # @rbs (String path, ?mode: Symbol, ?loader: SourceLoader) -> void
27
+ def initialize(path, mode: :default, loader: SourceLoader.new)
28
+ raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode)
29
+
30
+ @input_path = path
31
+ @mode = mode
32
+ @loader = loader
33
+ @attempted_paths = [File.expand_path(path)]
34
+ end
35
+
36
+ # @rbs () -> Resolution
37
+ def resolve
38
+ cached = @resolution
39
+ return cached if cached
40
+
41
+ prepare_resolution
42
+ parsed_root = parse_root(@root_path)
43
+ declarations, rules = expand_node(parsed_root, [])
44
+ root = AST::Root.new(
45
+ class_name: parsed_root.class_name, superclass: parsed_root.superclass,
46
+ declarations: declarations, rules: rules, user_code: parsed_root.user_code, loc: parsed_root.loc,
47
+ extended: parsed_root.extended, cst: parsed_root.cst
48
+ )
49
+ @loaded[@root_path] = true
50
+ @visiting.pop
51
+ @resolution = Resolution.new(
52
+ root: root, root_path: @root_path, root_directory: @root_directory,
53
+ files: @files, include_chains: @include_chains
54
+ )
55
+ end
56
+
57
+ # @rbs () -> Array[String]
58
+ def dependencies
59
+ resolve.files
60
+ end
61
+
62
+ # Source bytes actually consumed by the parser, in resolution order.
63
+ # @rbs () -> Array[GenerationInput]
64
+ def source_records
65
+ @loader.read_records
66
+ end
67
+
68
+ private
69
+
70
+ # @rbs () -> void
71
+ def prepare_resolution
72
+ @root_path = canonical_root
73
+ @root_directory = File.dirname(@root_path)
74
+ @files = [@root_path] #: Array[String]
75
+ @visiting = [@root_path] #: Array[String]
76
+ @loaded = {} #: Hash[String, bool]
77
+ chains = {} #: Hash[AST::Rule, Array[IR::source_provenance]]
78
+ @include_chains = chains.compare_by_identity
79
+ @attempted_paths = [File.expand_path(@input_path)]
80
+ end
81
+
82
+ # @rbs () -> String
83
+ def canonical_root
84
+ path = @loader.canonical_path(@input_path)
85
+ raise Ibex::Error, "#{@input_path}:1:1: root grammar must be a file" unless @loader.file?(path)
86
+
87
+ @loader.record_access(path, @input_path)
88
+ path
89
+ rescue SystemCallError => e
90
+ raise ResolutionIOError, "#{@input_path}:1:1: cannot read root grammar: #{e.message}"
91
+ end
92
+
93
+ # @rbs (String path) -> AST::Root
94
+ def parse_root(path)
95
+ Parser.new(@loader.read(path), file: path, mode: @mode).parse
96
+ rescue SystemCallError => e
97
+ raise ResolutionIOError, "#{path}:1:1: cannot read grammar: #{e.message}"
98
+ end
99
+
100
+ # @rbs (String path) -> AST::Fragment
101
+ def parse_fragment(path)
102
+ Parser.new(@loader.read(path), file: path, mode: :extended).parse_fragment
103
+ rescue SystemCallError => e
104
+ raise ResolutionIOError, "#{path}:1:1: cannot read fragment: #{e.message}"
105
+ end
106
+
107
+ # @rbs (AST::Root | AST::Fragment node, Array[IR::source_provenance] chain) ->
108
+ # [Array[AST::declaration], Array[AST::Rule]]
109
+ def expand_node(node, chain)
110
+ declarations = [] #: Array[AST::declaration]
111
+ rules = [] #: Array[AST::Rule]
112
+ node.declarations.each do |declaration|
113
+ if declaration.is_a?(AST::Include)
114
+ included_declarations, included_rules = expand_include(declaration, chain)
115
+ declarations.concat(included_declarations)
116
+ rules.concat(included_rules)
117
+ else
118
+ declarations << declaration
119
+ end
120
+ end
121
+ node.rules.each do |rule|
122
+ @include_chains[rule] = chain
123
+ rules << rule
124
+ end
125
+ [declarations, rules]
126
+ end
127
+
128
+ # @rbs (AST::Include include_node, Array[IR::source_provenance] chain) ->
129
+ # [Array[AST::declaration], Array[AST::Rule]]
130
+ def expand_include(include_node, chain)
131
+ target = canonical_include(include_node)
132
+ reject_cycle(include_node, target)
133
+ return [[], []] if @loaded[target]
134
+
135
+ @files << target
136
+ @visiting << target
137
+ fragment = parse_fragment(target)
138
+ next_chain = chain + [source_provenance(target)]
139
+ declarations, rules = expand_node(fragment, next_chain)
140
+ @visiting.pop
141
+ @loaded[target] = true
142
+ [declarations, rules]
143
+ end
144
+
145
+ # @rbs (AST::Include include_node) -> String
146
+ def canonical_include(include_node)
147
+ validate_include_path(include_node)
148
+ candidate = File.expand_path(include_node.path, File.dirname(include_node.loc.file))
149
+ @attempted_paths << candidate unless @attempted_paths.include?(candidate)
150
+ canonical = @loader.canonical_path(candidate)
151
+ unless @loader.file?(canonical)
152
+ fail_include(include_node, "include path is not a file: #{include_node.path.inspect}")
153
+ end
154
+
155
+ unless inside_root?(canonical)
156
+ message = "include resolves outside the root grammar directory: #{include_node.path.inspect}"
157
+ fail_include(include_node, message)
158
+ end
159
+ @loader.record_access(canonical, candidate)
160
+ canonical
161
+ rescue Errno::ENOENT, Errno::ENOTDIR
162
+ fail_include(include_node, "include file does not exist: #{include_node.path.inspect}")
163
+ rescue SystemCallError => e
164
+ message = "#{include_node.loc}: cannot read include #{include_node.path.inspect}: #{e.message}"
165
+ raise ResolutionIOError, message
166
+ end
167
+
168
+ # @rbs (AST::Include include_node) -> void
169
+ def validate_include_path(include_node)
170
+ path = include_node.path
171
+ fail_include(include_node, "include path must not be empty") if path.empty?
172
+ fail_include(include_node, "include path must not contain NUL") if path.include?("\0")
173
+ if path.start_with?("/") || path.match?(WINDOWS_ABSOLUTE)
174
+ fail_include(include_node, "include path must be relative")
175
+ end
176
+ if path.split(%r{[\\/]}).include?("..")
177
+ fail_include(include_node, "include path must not contain parent traversal")
178
+ end
179
+ return unless path.match?(GLOB_CHARACTERS)
180
+
181
+ fail_include(include_node, "include path must not contain glob metacharacters")
182
+ end
183
+
184
+ # @rbs (String path) -> bool
185
+ def inside_root?(path)
186
+ directory = File.dirname(path)
187
+ loop do
188
+ return true if directory == @root_directory
189
+
190
+ parent = File.dirname(directory)
191
+ return false if parent == directory
192
+
193
+ directory = parent
194
+ end
195
+ end
196
+
197
+ # @rbs (AST::Include include_node, String target) -> void
198
+ def reject_cycle(include_node, target)
199
+ start = @visiting.index(target)
200
+ return unless start
201
+
202
+ cycle = @visiting.drop(start) + [target]
203
+ fail_include(include_node, "include cycle: #{cycle.join(' -> ')}")
204
+ end
205
+
206
+ # @rbs (String file) -> IR::source_provenance
207
+ def source_provenance(file)
208
+ { file: file, root: @root_directory, byte_span: nil }
209
+ end
210
+
211
+ # @rbs (AST::Include include_node, String message) -> bot
212
+ def fail_include(include_node, message)
213
+ raise Ibex::Error, "#{include_node.loc}: #{message}"
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Attaches immediately preceding lossless line-comment blocks to parsed rules.
6
+ class RuleDocumentation
7
+ EMPTY_SEGMENTS = Array.new(0).freeze #: Array[Segment]
8
+ private_constant :EMPTY_SEGMENTS
9
+
10
+ # @rbs (AST::Root | AST::Fragment node, SourceDocument document) -> (AST::Root | AST::Fragment)
11
+ def self.enrich(node, document)
12
+ new(document).enrich(node)
13
+ end
14
+
15
+ # @rbs (SourceDocument document) -> void
16
+ def initialize(document)
17
+ @segments_by_line = index_segments(document) #: Hash[Integer, Array[Segment]]
18
+ @inline_lines = document.tokens.filter_map do |token|
19
+ token.location.line if token.type == :inline
20
+ end #: Array[Integer]
21
+ end
22
+
23
+ # @rbs (AST::Root | AST::Fragment node) -> (AST::Root | AST::Fragment)
24
+ def enrich(node)
25
+ rules = node.rules.map { |rule| documented_rule(rule) }
26
+ if node.is_a?(AST::Root)
27
+ AST::Root.new(
28
+ class_name: node.class_name, superclass: node.superclass, declarations: node.declarations,
29
+ rules: rules, user_code: node.user_code, loc: node.loc, extended: node.extended, cst: node.cst
30
+ )
31
+ else
32
+ AST::Fragment.new(declarations: node.declarations, rules: rules, loc: node.loc)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # @rbs (AST::Rule rule) -> AST::Rule
39
+ def documented_rule(rule)
40
+ AST::Rule.new(
41
+ lhs: rule.lhs, parameters: rule.parameters, alternatives: rule.alternatives, loc: rule.loc,
42
+ documentation: documentation_before(documentation_anchor(rule)), inline: rule.inline
43
+ )
44
+ end
45
+
46
+ # @rbs (AST::Rule rule) -> Integer
47
+ def documentation_anchor(rule)
48
+ return rule.loc.line unless rule.inline
49
+
50
+ @inline_lines.reverse_each.find { |line| line <= rule.loc.line } || rule.loc.line
51
+ end
52
+
53
+ # @rbs (Integer rule_line) -> String?
54
+ def documentation_before(rule_line)
55
+ lines = [] #: Array[String]
56
+ line = rule_line - 1
57
+ while line.positive?
58
+ content = documentation_line(line)
59
+ break unless content
60
+
61
+ lines.unshift(content)
62
+ line -= 1
63
+ end
64
+ lines.empty? ? nil : lines.join("\n").freeze
65
+ end
66
+
67
+ # @rbs (Integer line) -> String?
68
+ def documentation_line(line)
69
+ segments = @segments_by_line.fetch(line, EMPTY_SEGMENTS)
70
+ comments = segments.select { |segment| segment.kind == :line_comment }
71
+ return unless comments.length == 1
72
+
73
+ comment = comments.fetch(0)
74
+ return unless comment.text.start_with?("##")
75
+ return unless segments.all? { |segment| documentation_line_segment?(segment, comment) }
76
+
77
+ comment.text.delete_prefix("##").delete_prefix(" ")
78
+ end
79
+
80
+ # @rbs (Segment segment, Segment comment) -> bool
81
+ def documentation_line_segment?(segment, comment)
82
+ segment.equal?(comment) || %i[whitespace newline].include?(segment.kind)
83
+ end
84
+
85
+ # @rbs (SourceDocument document) -> Hash[Integer, Array[Segment]]
86
+ def index_segments(document)
87
+ indexed = Hash.new { |hash, line| hash[line] = [] } #: Hash[Integer, Array[Segment]]
88
+ document.cst.each do |segment|
89
+ covered_lines(segment).each { |line| indexed[line] << segment }
90
+ end
91
+ indexed
92
+ end
93
+
94
+ # @rbs (Segment segment) -> Array[Integer]
95
+ def covered_lines(segment)
96
+ first = segment.span.start.line
97
+ return [first] if segment.kind == :newline
98
+
99
+ (first..segment.span.finish.line).to_a
100
+ end
101
+ end
102
+ end
103
+ end