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,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Builds source occurrences and hover metadata from lossless frontend documents.
6
+ class SymbolIndexBuilder
7
+ include SymbolIndexSourceQueries
8
+ include SymbolIndexPrecedenceReferences
9
+
10
+ # @rbs (DocumentStore store, Array[String] files) -> void
11
+ def initialize(store, files)
12
+ @store = store
13
+ @files = files
14
+ @occurrences = [] #: Array[SymbolOccurrence]
15
+ @documents = {} #: Hash[String, Frontend::SourceDocument]
16
+ @global_kinds = {} #: Hash[String, Symbol]
17
+ @rule_data = {} #: Hash[String, Hash[Symbol, untyped]]
18
+ @terminal_data = Hash.new { |hash, key| hash[key] = {} } #: Hash[String, Hash[Symbol, untyped]]
19
+ end
20
+
21
+ # @rbs () -> [Array[SymbolOccurrence], Hash[String, Frontend::SourceDocument]]
22
+ def build
23
+ collect_documents
24
+ # Definitions from every closure file must exist before references are classified.
25
+ # rubocop:disable Style/CombinableLoops
26
+ @documents.each { |path, document| collect_definitions(path, document) }
27
+ @documents.each { |path, document| collect_references(path, document) }
28
+ # rubocop:enable Style/CombinableLoops
29
+ [@occurrences.freeze, @documents.freeze]
30
+ end
31
+
32
+ private
33
+
34
+ # @rbs () -> void
35
+ def collect_documents
36
+ @files.each do |path|
37
+ snapshot = @store.snapshot_for(path)
38
+ document = snapshot&.fetch(:document)
39
+ @documents[path] = document if document
40
+ end
41
+ end
42
+
43
+ # @rbs (String path, Frontend::SourceDocument document) -> void
44
+ def collect_definitions(path, document)
45
+ ast = document.ast
46
+ return unless ast
47
+
48
+ declarations = ast.declarations
49
+ declarations.each_with_index do |declaration, index|
50
+ following = declarations[index + 1]&.loc || ast.rules.first&.loc
51
+ collect_declaration(path, document, declaration, following)
52
+ end
53
+ ast.rules.each { |rule| collect_rule_definition(path, document, rule) }
54
+ end
55
+
56
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration,
57
+ # Frontend::Location? following) -> void
58
+ def collect_declaration(path, document, declaration, following)
59
+ tokens = tokens_between(document, declaration.loc, following)
60
+ case declaration
61
+ when Frontend::AST::Tokens
62
+ add_named_tokens(path, tokens.drop(1), declaration.names, :terminal, :definition)
63
+ when Frontend::AST::DisplayName
64
+ @terminal_data[declaration.name][:display] = declaration.value
65
+ when Frontend::AST::SemanticType
66
+ @terminal_data[declaration.name][:type] = declaration.value
67
+ when Frontend::AST::Precedence
68
+ collect_precedence(declaration)
69
+ when Frontend::AST::Include
70
+ collect_include(path, tokens, declaration)
71
+ end
72
+ end
73
+
74
+ # @rbs (Frontend::AST::Precedence declaration) -> void
75
+ def collect_precedence(declaration)
76
+ declaration.levels.each_with_index do |level, index|
77
+ level.symbols.each do |name|
78
+ @terminal_data[name][:precedence] = {
79
+ direction: declaration.direction, associativity: level.associativity, level: index
80
+ }
81
+ end
82
+ end
83
+ end
84
+
85
+ # @rbs (String path, Array[Frontend::Token] tokens, Frontend::AST::Include declaration) -> void
86
+ def collect_include(path, tokens, declaration)
87
+ token = tokens.find { |candidate| candidate.type == :literal }
88
+ return unless token&.span
89
+
90
+ target = canonical_include_target(declaration)
91
+ return unless target
92
+
93
+ add_occurrence(
94
+ name: declaration.path, kind: :include, role: :reference, key: [:include, target],
95
+ path: path, span: token.span, data: { target: target }
96
+ )
97
+ end
98
+
99
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule) -> void
100
+ def collect_rule_definition(path, document, rule)
101
+ token = token_at(document, rule.loc, rule.lhs)
102
+ return unless token&.span
103
+
104
+ @global_kinds[rule.lhs] ||= :rule
105
+ signature = "#{rule.lhs}#{parameter_signature(rule.parameters)}"
106
+ signature = "%inline #{signature}" if rule.inline
107
+ data = {
108
+ signature: signature,
109
+ documentation: rule.documentation,
110
+ inline: rule.inline,
111
+ parameters: rule.parameters
112
+ }
113
+ @rule_data[rule.lhs] ||= data
114
+ add_occurrence(
115
+ name: rule.lhs, kind: :rule, role: :definition, key: global_key(rule.lhs),
116
+ path: path, span: token.span, data: data
117
+ )
118
+ collect_parameters(path, document, rule, token)
119
+ end
120
+
121
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule,
122
+ # Frontend::Token lhs_token) -> void
123
+ def collect_parameters(path, document, rule, lhs_token)
124
+ return if rule.parameters.empty?
125
+
126
+ rule_id = lhs_token.span&.start_byte || 0
127
+ rule.parameters.zip(parameter_tokens(document, lhs_token)).each do |parameter, token|
128
+ next unless token&.span
129
+
130
+ add_occurrence(
131
+ name: parameter, kind: :parameter, role: :definition,
132
+ key: parameter_key(path, rule_id, parameter), path: path, span: token.span,
133
+ data: { rule: rule.lhs }
134
+ )
135
+ end
136
+ end
137
+
138
+ # @rbs (String path, Frontend::SourceDocument document) -> void
139
+ def collect_references(path, document)
140
+ ast = document.ast
141
+ return unless ast
142
+
143
+ ast.declarations.each_with_index do |declaration, index|
144
+ following = ast.declarations[index + 1]&.loc || ast.rules.first&.loc
145
+ collect_declaration_references(path, document, declaration, following)
146
+ end
147
+ ast.rules.each_with_index do |rule, index|
148
+ collect_rule_references(path, document, rule, ast.rules[index + 1]&.loc)
149
+ end
150
+ end
151
+
152
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration,
153
+ # Frontend::Location? following) -> void
154
+ def collect_declaration_references(path, document, declaration, following)
155
+ names = declaration_reference_names(declaration)
156
+ return if names.empty?
157
+
158
+ remaining = tokens_between(document, declaration.loc, following).drop(1)
159
+ names.each do |name|
160
+ index = remaining.index { |token| token.value == name }
161
+ token = index && remaining.delete_at(index)
162
+ add_global_reference(path, token, name) if token
163
+ end
164
+ end
165
+
166
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule,
167
+ # Frontend::Location? following_rule) -> void
168
+ def collect_rule_references(path, document, rule, following_rule)
169
+ definition = token_at(document, rule.loc, rule.lhs)
170
+ rule_id = definition&.span&.start_byte || 0
171
+ rule.alternatives.each_with_index do |alternative, index|
172
+ alternative.items.each { |item| collect_item(path, document, item, rule, rule_id) }
173
+ following = rule.alternatives[index + 1]&.loc || following_rule
174
+ collect_alternative_precedence(path, document, alternative, following, rule, rule_id)
175
+ end
176
+ end
177
+
178
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::item item,
179
+ # Frontend::AST::Rule rule, Integer rule_id) -> void
180
+ def collect_item(path, document, item, rule, rule_id)
181
+ case item
182
+ when Frontend::AST::SymbolReference, Frontend::AST::ParameterizedReference
183
+ collect_named_item(path, document, item, rule, rule_id)
184
+ item.arguments.each { |child| collect_item(path, document, child, rule, rule_id) } if
185
+ item.is_a?(Frontend::AST::ParameterizedReference)
186
+ when Frontend::AST::Optional, Frontend::AST::Star, Frontend::AST::Plus
187
+ collect_item(path, document, item.item, rule, rule_id)
188
+ when Frontend::AST::Group
189
+ item.alternatives.flatten.each { |child| collect_item(path, document, child, rule, rule_id) }
190
+ when Frontend::AST::SeparatedList
191
+ collect_item(path, document, item.item, rule, rule_id)
192
+ collect_item(path, document, item.separator, rule, rule_id)
193
+ end
194
+ end
195
+
196
+ # @rbs (String path, Frontend::SourceDocument document,
197
+ # Frontend::AST::SymbolReference | Frontend::AST::ParameterizedReference item,
198
+ # Frontend::AST::Rule rule, Integer rule_id) -> void
199
+ def collect_named_item(path, document, item, rule, rule_id)
200
+ token = token_at(document, item.loc, item.name)
201
+ return unless token&.span
202
+
203
+ add_rule_reference(path, token, item.name, rule, rule_id)
204
+ end
205
+
206
+ # @rbs (String path, Frontend::Token token, String name) -> void
207
+ def add_global_reference(path, token, name)
208
+ return unless token.span
209
+
210
+ kind = @global_kinds[name] || :terminal
211
+ data = symbol_data(kind, name)
212
+ add_occurrence(name: name, kind: kind, role: :reference, key: global_key(name),
213
+ path: path, span: token.span, data: data)
214
+ end
215
+
216
+ # @rbs (Symbol kind, String name) -> Hash[Symbol, untyped]
217
+ def symbol_data(kind, name)
218
+ data = kind == :rule ? @rule_data[name] : @terminal_data[name]
219
+ empty = {} #: Hash[Symbol, untyped]
220
+ data || empty
221
+ end
222
+
223
+ # @rbs (String path, Array[Frontend::Token] tokens, Array[String] names, Symbol kind, Symbol role) -> void
224
+ def add_named_tokens(path, tokens, names, kind, role)
225
+ remaining = tokens.dup
226
+ names.each do |name|
227
+ index = remaining.index { |token| token.value == name }
228
+ token = index && remaining.delete_at(index)
229
+ next unless token&.span
230
+
231
+ @global_kinds[name] ||= kind
232
+ add_occurrence(name: name, kind: kind, role: role, key: global_key(name),
233
+ path: path, span: token.span, data: @terminal_data[name])
234
+ end
235
+ end
236
+
237
+ # @rbs (**untyped attributes) -> void
238
+ def add_occurrence(**attributes)
239
+ @occurrences << SymbolOccurrence.new(**attributes)
240
+ end
241
+
242
+ # @rbs (String name) -> Array[untyped]
243
+ def global_key(name)
244
+ [:symbol, name]
245
+ end
246
+
247
+ # @rbs (String path, Integer rule_id, String name) -> Array[untyped]
248
+ def parameter_key(path, rule_id, name)
249
+ [:parameter, path, rule_id, name]
250
+ end
251
+
252
+ # @rbs (Array[String] parameters) -> String
253
+ def parameter_signature(parameters)
254
+ parameters.empty? ? "" : "(#{parameters.join(', ')})"
255
+ end
256
+
257
+ # @rbs (Frontend::AST::Include declaration) -> String?
258
+ def canonical_include_target(declaration)
259
+ candidate = File.expand_path(declaration.path, File.dirname(declaration.loc.file))
260
+ target = @store.loader.canonical_path(candidate, allow_missing: true)
261
+ @store.workspace.root_for(target) && target
262
+ rescue SystemCallError
263
+ nil
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Indexes rule-scoped item and alternative precedence references.
6
+ module SymbolIndexPrecedenceReferences
7
+ private
8
+
9
+ # @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Alternative alternative,
10
+ # Frontend::Location? following, Frontend::AST::Rule rule, Integer rule_id) -> void
11
+ def collect_alternative_precedence(path, document, alternative, following, rule, rule_id)
12
+ # @type self: SymbolIndexBuilder
13
+ name = alternative.precedence
14
+ return unless name
15
+
16
+ tokens = tokens_between(document, alternative.loc, following)
17
+ index = (0...(tokens.length - 1)).to_a.reverse.find do |candidate|
18
+ tokens.fetch(candidate).value == "=" && tokens.fetch(candidate + 1).value == name
19
+ end
20
+ token = index && tokens[index + 1]
21
+ add_rule_reference(path, token, name, rule, rule_id) if token
22
+ end
23
+
24
+ # @rbs (String path, Frontend::Token token, String name, Frontend::AST::Rule rule,
25
+ # Integer rule_id) -> void
26
+ def add_rule_reference(path, token, name, rule, rule_id)
27
+ # @type self: SymbolIndexBuilder
28
+ return unless token.span
29
+
30
+ if rule.parameters.include?(name)
31
+ key = parameter_key(path, rule_id, name)
32
+ kind = :parameter
33
+ data = { rule: rule.lhs }
34
+ else
35
+ key = global_key(name)
36
+ kind = @global_kinds[name] || :rule
37
+ data = symbol_data(kind, name)
38
+ end
39
+ add_occurrence(name: name, kind: kind, role: :reference, key: key,
40
+ path: path, span: token.span, data: data)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Locates lossless frontend tokens used by the symbol index.
6
+ module SymbolIndexSourceQueries
7
+ private
8
+
9
+ # @rbs (Frontend::SourceDocument document, Frontend::Location start,
10
+ # Frontend::Location? finish) -> Array[Frontend::Token]
11
+ def tokens_between(document, start, finish)
12
+ document.tokens.select do |token|
13
+ after = !location_before?(token.location, start)
14
+ before = finish.nil? || location_before?(token.location, finish)
15
+ after && before
16
+ end
17
+ end
18
+
19
+ # @rbs (Frontend::SourceDocument document, Frontend::Location location, String value) -> Frontend::Token?
20
+ def token_at(document, location, value)
21
+ document.tokens.find do |token|
22
+ token.location.line == location.line && token.location.column == location.column && token.value == value
23
+ end
24
+ end
25
+
26
+ # @rbs (Frontend::SourceDocument document, Frontend::Token lhs_token) -> Array[Frontend::Token]
27
+ def parameter_tokens(document, lhs_token)
28
+ start_index = document.tokens.index(lhs_token)
29
+ return [] unless start_index
30
+
31
+ tail = document.tokens.drop(start_index + 1)
32
+ return [] unless tail.first&.type == :"("
33
+
34
+ tail.take_while { |token| token.type != :")" }.select { |token| token.type == :identifier }
35
+ end
36
+
37
+ # @rbs (Frontend::AST::declaration declaration) -> Array[String]
38
+ def declaration_reference_names(declaration)
39
+ case declaration
40
+ when Frontend::AST::Start, Frontend::AST::OnErrorReduce
41
+ declaration.names
42
+ when Frontend::AST::Recovery
43
+ declaration.sync_tokens
44
+ when Frontend::AST::DisplayName, Frontend::AST::SemanticType
45
+ [declaration.name]
46
+ when Frontend::AST::Convert
47
+ declaration.pairs.map(&:name)
48
+ when Frontend::AST::Precedence
49
+ declaration.levels.flat_map(&:symbols)
50
+ else
51
+ []
52
+ end
53
+ end
54
+
55
+ # @rbs (Frontend::Location left, Frontend::Location right) -> bool
56
+ def location_before?(left, right)
57
+ left.line < right.line || (left.line == right.line && left.column < right.column)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # A source-backed grammar symbol definition or reference.
6
+ SymbolOccurrence = Struct.new(
7
+ :name, #: String
8
+ :kind, #: Symbol
9
+ :role, #: Symbol
10
+ :key, #: Array[untyped]
11
+ :path, #: String
12
+ :span, #: Frontend::SourceSpan
13
+ :data, #: Hash[Symbol, untyped]
14
+ keyword_init: true
15
+ )
16
+ end
17
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Reads and writes bounded LSP JSON-RPC messages over Content-Length framing.
6
+ class Transport
7
+ HEADER_SEPARATOR = "\r\n\r\n"
8
+
9
+ # @rbs (untyped input, untyped output) -> void
10
+ def initialize(input, output)
11
+ @input = input
12
+ @output = output
13
+ @buffer = String.new(encoding: Encoding::BINARY)
14
+ end
15
+
16
+ # @rbs () -> Hash[String, untyped]?
17
+ def read_message
18
+ header = read_header
19
+ return unless header
20
+
21
+ length = content_length(header)
22
+ body = read_bytes(length)
23
+ parsed = JSON.parse(body)
24
+ raise ProtocolError.new("JSON-RPC message must be an object", code: -32_600) unless parsed.is_a?(Hash)
25
+
26
+ parsed
27
+ rescue JSON::ParserError => e
28
+ raise ProtocolError.new("malformed JSON: #{e.message}", code: -32_700)
29
+ end
30
+
31
+ # @rbs (Hash[String, untyped] message) -> void
32
+ def write_message(message)
33
+ body = JSON.generate(message)
34
+ if body.bytesize > Limits::MAX_OUTPUT_BYTES
35
+ raise ProtocolError.new("JSON-RPC output exceeds #{Limits::MAX_OUTPUT_BYTES} bytes", code: -32_603)
36
+ end
37
+
38
+ @output.write("Content-Length: #{body.bytesize}\r\n\r\n")
39
+ @output.write(body)
40
+ @output.flush if @output.respond_to?(:flush)
41
+ end
42
+
43
+ private
44
+
45
+ # @rbs () -> String?
46
+ def read_header
47
+ loop do
48
+ index = @buffer.index(HEADER_SEPARATOR)
49
+ if index
50
+ if index > Limits::MAX_HEADER_BYTES
51
+ raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes",
52
+ code: -32_700, fatal: true)
53
+ end
54
+
55
+ header = @buffer.byteslice(0, index) || ""
56
+ @buffer = @buffer.byteslice(index + HEADER_SEPARATOR.bytesize, @buffer.bytesize) || +""
57
+ return header
58
+ end
59
+ if @buffer.bytesize > Limits::MAX_HEADER_BYTES
60
+ raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes",
61
+ code: -32_700, fatal: true)
62
+ end
63
+
64
+ chunk = read_chunk(4096)
65
+ return nil if chunk.nil? && @buffer.empty?
66
+ raise ProtocolError.new("truncated JSON-RPC header", code: -32_700, fatal: true) unless chunk
67
+
68
+ @buffer << chunk.b
69
+ end
70
+ end
71
+
72
+ # @rbs (String header) -> Integer
73
+ def content_length(header)
74
+ values = header.split("\r\n").filter_map do |line|
75
+ name, value = line.split(":", 2)
76
+ raise ProtocolError.new("malformed JSON-RPC header", code: -32_700, fatal: true) unless value
77
+
78
+ value.strip if name&.casecmp?("Content-Length")
79
+ end
80
+ unless values.one? && values.fetch(0).match?(/\A\d+\z/)
81
+ raise ProtocolError.new("exactly one numeric Content-Length header is required",
82
+ code: -32_700, fatal: true)
83
+ end
84
+
85
+ length = Integer(values.fetch(0), 10)
86
+ if length > Limits::MAX_MESSAGE_BYTES
87
+ raise ProtocolError.new("JSON-RPC message exceeds #{Limits::MAX_MESSAGE_BYTES} bytes",
88
+ code: -32_700, fatal: true)
89
+ end
90
+ length
91
+ end
92
+
93
+ # @rbs (Integer length) -> String
94
+ def read_bytes(length)
95
+ while @buffer.bytesize < length
96
+ chunk = read_chunk([4096, length - @buffer.bytesize].min)
97
+ raise ProtocolError.new("truncated JSON-RPC body", code: -32_700, fatal: true) unless chunk
98
+
99
+ @buffer << chunk.b
100
+ end
101
+ body = @buffer.byteslice(0, length) || ""
102
+ @buffer = @buffer.byteslice(length, @buffer.bytesize) || +""
103
+ body.force_encoding(Encoding::UTF_8)
104
+ end
105
+
106
+ # IO#read(length) may wait for the entire length on a live pipe. readpartial
107
+ # lets the stdio server process the bytes that are already available.
108
+ # @rbs (Integer length) -> String?
109
+ def read_chunk(length)
110
+ return @input.read(length) unless @input.respond_to?(:readpartial)
111
+
112
+ @input.readpartial(length)
113
+ rescue EOFError
114
+ nil
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Converts file URIs to canonical paths constrained to initialized workspace roots.
6
+ class Workspace
7
+ attr_reader :roots #: Array[String]
8
+
9
+ # @rbs (Array[String] root_uris, Frontend::SourceLoader loader) -> void
10
+ def initialize(root_uris, loader)
11
+ raise ProtocolError.new("initialize requires a workspace root", code: -32_602) if root_uris.empty?
12
+
13
+ @loader = loader
14
+ @roots = root_uris.map { |uri| canonical_root(uri) }.uniq.sort.freeze
15
+ end
16
+
17
+ # @rbs (String uri, ?allow_missing: bool) -> String
18
+ def path(uri, allow_missing: true)
19
+ parsed = parse_file_uri(uri)
20
+ decoded = decode_path(parsed.path)
21
+
22
+ canonical = @loader.canonical_path(decoded, allow_missing: allow_missing)
23
+ return canonical if root_for(canonical)
24
+
25
+ raise ProtocolError.new("document is outside initialized workspace roots: #{uri}", code: -32_602)
26
+ rescue URI::InvalidURIError, ArgumentError => e
27
+ raise ProtocolError.new("invalid file URI #{uri.inspect}: #{e.message}", code: -32_602)
28
+ rescue SystemCallError => e
29
+ raise ProtocolError.new("cannot resolve file URI #{uri.inspect}: #{e.message}", code: -32_602)
30
+ end
31
+
32
+ # @rbs (String path) -> String
33
+ def uri(path)
34
+ canonical = @loader.canonical_path(path, allow_missing: true)
35
+ parser = begin
36
+ URI::RFC2396_PARSER
37
+ rescue NameError
38
+ URI::DEFAULT_PARSER
39
+ end
40
+ "file://#{parser.escape(canonical)}"
41
+ end
42
+
43
+ # @rbs (String path) -> String?
44
+ def root_for(path)
45
+ roots.select { |root| inside?(path, root) }.max_by(&:length)
46
+ end
47
+
48
+ private
49
+
50
+ # URI is a stdlib boundary whose implementation class varies by scheme.
51
+ # @rbs (String uri) -> untyped
52
+ def parse_file_uri(uri)
53
+ validate_raw_authority!(uri)
54
+ parsed = URI.parse(uri)
55
+ path = parsed.path
56
+ local_host = parsed.host.nil? || parsed.host.empty? || parsed.host.casecmp?("localhost")
57
+ unless parsed.scheme == "file" && local_host &&
58
+ parsed.query.nil? && parsed.fragment.nil? && path&.start_with?("/")
59
+ raise ArgumentError, "only absolute local file URIs are supported"
60
+ end
61
+
62
+ parsed
63
+ end
64
+
65
+ # Ruby's URI::File may discard userinfo and port, so validate the raw authority first.
66
+ # @rbs (String uri) -> void
67
+ def validate_raw_authority!(uri)
68
+ match = uri.match(%r{\Afile://([^/]*)}i)
69
+ return unless match
70
+
71
+ authority = match[1] || ""
72
+ return if authority.empty? || authority.casecmp?("localhost")
73
+
74
+ raise ArgumentError, "only an empty authority or localhost is supported"
75
+ end
76
+
77
+ # @rbs (String uri) -> String
78
+ def canonical_root(uri)
79
+ parsed = parse_file_uri(uri)
80
+ decoded = decode_path(parsed.path)
81
+ canonical = File.realpath(decoded)
82
+ unless File.directory?(canonical)
83
+ raise ProtocolError.new("workspace root is not a directory: #{uri}", code: -32_602)
84
+ end
85
+
86
+ canonical
87
+ rescue URI::InvalidURIError, ArgumentError, SystemCallError => e
88
+ raise ProtocolError.new("invalid workspace root #{uri.inspect}: #{e.message}", code: -32_602)
89
+ end
90
+
91
+ # @rbs (String encoded) -> String
92
+ def decode_path(encoded)
93
+ raise ArgumentError, "file URI contains an invalid percent escape" if
94
+ encoded.match?(/%(?![0-9A-Fa-f]{2})/)
95
+ if encoded.match?(/%(?:2f|5c|00)/i)
96
+ raise ArgumentError, "file URI must not percent-encode a path separator or NUL"
97
+ end
98
+
99
+ parser = begin
100
+ URI::RFC2396_PARSER
101
+ rescue NameError
102
+ URI::DEFAULT_PARSER
103
+ end
104
+ decoded = parser.unescape(encoded)
105
+ raise ArgumentError, "file URI path must be valid UTF-8" unless decoded.valid_encoding?
106
+ raise ArgumentError, "file URI path must not contain NUL" if decoded.include?("\0")
107
+ raise ArgumentError, "file URI path must not contain parent traversal" if
108
+ decoded.split(%r{[\\/]}).include?("..")
109
+
110
+ decoded
111
+ end
112
+
113
+ # @rbs (String path, String root) -> bool
114
+ def inside?(path, root)
115
+ cursor = path
116
+ loop do
117
+ return true if cursor == root
118
+
119
+ parent = File.dirname(cursor)
120
+ return false if parent == cursor
121
+
122
+ cursor = parent
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end