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,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Owns open buffers, parsed snapshots, include closures, and reverse dependencies.
6
+ class DocumentStore
7
+ include DocumentStoreValidation
8
+ include DocumentStoreDiagnostics
9
+
10
+ # @rbs!
11
+ # type snapshot = {
12
+ # uri: String,
13
+ # path: String,
14
+ # version: Integer?,
15
+ # source: String,
16
+ # open: bool,
17
+ # document: Frontend::SourceDocument?
18
+ # }
19
+ # type publication = {
20
+ # uri: String,
21
+ # ?version: Integer,
22
+ # diagnostics: Array[WorkspaceAnalyzer::lsp_diagnostic]
23
+ # }
24
+
25
+ attr_reader :loader #: Frontend::SourceLoader
26
+ attr_reader :workspace #: Workspace
27
+
28
+ # @rbs (Workspace workspace, Frontend::SourceLoader loader) -> void
29
+ def initialize(workspace, loader)
30
+ @workspace = workspace
31
+ @loader = loader
32
+ @analyzer = WorkspaceAnalyzer.new(workspace, loader)
33
+ @snapshots = {} #: Hash[String, snapshot]
34
+ @roots = {} #: Hash[String, bool]
35
+ @closures = {} #: Hash[String, Array[String]]
36
+ @reverse_dependencies = {} #: Hash[String, Array[String]]
37
+ @resolutions = {} #: Hash[String, Frontend::Resolution]
38
+ @document_diagnostics = {} #: Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]
39
+ @root_diagnostics = {} #: Hash[String, Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]]
40
+ end
41
+
42
+ # @rbs (String uri, Integer version, String source) -> Array[publication]
43
+ def open(uri, version, source)
44
+ path = workspace.path(uri)
45
+ raise ProtocolError.new("document is already open: #{uri}", code: -32_602) if @snapshots.dig(path, :open)
46
+
47
+ validate_version(version)
48
+ validate_source(source)
49
+ loader.set_overlay(path, source)
50
+ @snapshots[path] = snapshot(uri, path, version, source, true, nil)
51
+ refresh(path)
52
+ end
53
+
54
+ # @rbs (String uri, Integer version, String source) -> Array[publication]
55
+ def change(uri, version, source)
56
+ path = workspace.path(uri)
57
+ current = open_snapshot(path, uri)
58
+ validate_version(version)
59
+ unless current.fetch(:version) && version > current.fetch(:version)
60
+ raise ProtocolError.new("document version must increase monotonically", code: -32_602)
61
+ end
62
+
63
+ validate_source(source)
64
+ loader.set_overlay(path, source)
65
+ @snapshots[path] = snapshot(uri, path, version, source, true, nil)
66
+ refresh(path)
67
+ end
68
+
69
+ # @rbs (String uri, ?source: String?) -> Array[publication]
70
+ def save(uri, source: nil)
71
+ path = workspace.path(uri)
72
+ current = open_snapshot(path, uri)
73
+ if source
74
+ validate_source(source)
75
+ loader.set_overlay(path, source)
76
+ current = snapshot(uri, path, current.fetch(:version), source, true, nil)
77
+ @snapshots[path] = current
78
+ end
79
+ refresh(path)
80
+ end
81
+
82
+ # Clear the open version first, then replace it with a disk snapshot if one exists.
83
+ # @rbs (String uri) -> Array[publication]
84
+ def close(uri)
85
+ path = workspace.path(uri)
86
+ open_snapshot(path, uri)
87
+ clear = { uri: uri, diagnostics: [] } #: publication
88
+ old_files = files_for(path)
89
+ was_root = @roots[path]
90
+ disk_path = disk_path_for_close(path, uri)
91
+ disk_source = loader.disk_source(disk_path) if disk_path && loader.disk_file?(disk_path)
92
+ loader.delete_overlay(path)
93
+ if disk_source
94
+ @snapshots[path] = snapshot(uri, path, nil, disk_source, false, nil)
95
+ if was_root
96
+ analyzed = @analyzer.analyze(path)
97
+ update_snapshot(path, analyzed)
98
+ remove_root_state(path)
99
+ [clear] + publish_paths((old_files + [path]).uniq)
100
+ else
101
+ [clear] + refresh(path)
102
+ end
103
+ else
104
+ remove_path(path)
105
+ [clear] + refresh_dependents(path)
106
+ end
107
+ end
108
+
109
+ # @rbs (String path) -> snapshot?
110
+ def snapshot_for(path)
111
+ @snapshots[path]
112
+ end
113
+
114
+ # Preserve the URI spelling used to open a document; closed files use their canonical URI.
115
+ # @rbs (String path) -> String
116
+ def uri_for(path)
117
+ entry = @snapshots[path]
118
+ return entry.fetch(:uri) if entry&.fetch(:open)
119
+
120
+ workspace.uri(path)
121
+ end
122
+
123
+ # @rbs (String path) -> Array[String]
124
+ def files_for(path)
125
+ roots = affected_roots(path)
126
+ files = roots.flat_map { |root| @closures.fetch(root, [root]) }
127
+ (files + [path]).uniq
128
+ end
129
+
130
+ # @rbs (String path) -> Array[Frontend::Resolution]
131
+ def resolutions_for(path)
132
+ affected_roots(path).filter_map { |root| @resolutions[root] }
133
+ end
134
+
135
+ # @rbs (String path) -> Array[String]
136
+ def roots_for(path)
137
+ affected_roots(path)
138
+ end
139
+
140
+ private
141
+
142
+ # @rbs (String path) -> Array[publication]
143
+ def refresh(path)
144
+ old_files = files_for(path)
145
+ previous_roots = affected_roots(path)
146
+ was_root = @roots[path]
147
+ analyzed = @analyzer.analyze(path)
148
+ update_snapshot(path, analyzed)
149
+ update_root_membership(path, analyzed.fetch(:document))
150
+ remove_root_state(path) if was_root && !@roots[path]
151
+ roots = (previous_roots + affected_roots(path)).uniq
152
+ roots.delete(path) unless @roots[path]
153
+ roots.each { |root| refresh_root(root) }
154
+ publish_paths((old_files + files_for(path) + [path]).uniq)
155
+ end
156
+
157
+ # @rbs (String path) -> Array[publication]
158
+ def refresh_dependents(path)
159
+ roots = affected_roots(path)
160
+ roots.each { |root| refresh_root(root) }
161
+ publish_paths(roots.flat_map { |root| @closures.fetch(root, [root]) }.uniq)
162
+ end
163
+
164
+ # @rbs (String root) -> void
165
+ def refresh_root(root)
166
+ files, analyzed = @analyzer.closure(root)
167
+ @closures[root] = files
168
+ rebuild_reverse_dependencies
169
+ analyzed.each { |path, result| update_snapshot(path, result, own_diagnostics: false) }
170
+ parse_failed = analyzed.any? do |path, result|
171
+ !result.fetch(:diagnostics).empty? && loader.file?(path)
172
+ end
173
+ resolution, errors = parse_failed ? [nil, []] : @analyzer.resolve(root)
174
+ resolution ? @resolutions[root] = resolution : @resolutions.delete(root)
175
+ replace_root_diagnostics(root, files, analyzed, errors)
176
+ end
177
+
178
+ # @rbs () -> void
179
+ def rebuild_reverse_dependencies
180
+ reverse = Hash.new { |hash, key| hash[key] = [] } #: Hash[String, Array[String]]
181
+ @closures.each { |root, files| files.each { |file| reverse[file] << root } }
182
+ @reverse_dependencies = reverse
183
+ end
184
+
185
+ # @rbs (String path) -> Array[String]
186
+ def affected_roots(path)
187
+ roots = @reverse_dependencies.fetch(path, empty_paths).dup
188
+ roots << path if @roots[path]
189
+ roots.uniq
190
+ end
191
+
192
+ # @rbs (String path, WorkspaceAnalyzer::analyzed_document analyzed, ?own_diagnostics: bool) -> void
193
+ def update_snapshot(path, analyzed, own_diagnostics: true)
194
+ current = @snapshots[path]
195
+ uri = current&.fetch(:uri) || workspace.uri(path)
196
+ version = current&.fetch(:version)
197
+ open = current&.fetch(:open) || false
198
+ source = open ? current.fetch(:source) : analyzed.fetch(:source)
199
+ document = analyzed.fetch(:document)
200
+ @snapshots[path] = snapshot(uri, path, version, source, open, document)
201
+ @document_diagnostics[path] = analyzed.fetch(:diagnostics) if own_diagnostics
202
+ end
203
+
204
+ # @rbs (String path, Frontend::SourceDocument? document) -> void
205
+ def update_root_membership(path, document)
206
+ if document&.ast.is_a?(Frontend::AST::Root)
207
+ @roots[path] = true
208
+ else
209
+ @roots.delete(path)
210
+ end
211
+ end
212
+
213
+ # @rbs (Array[String] paths) -> Array[publication]
214
+ def publish_paths(paths)
215
+ paths.filter_map do |path|
216
+ entry = @snapshots[path]
217
+ next unless entry
218
+
219
+ publication = {
220
+ uri: entry.fetch(:uri),
221
+ diagnostics: aggregated_diagnostics(path)
222
+ } #: publication
223
+ publication[:version] = entry.fetch(:version) if entry.fetch(:open) && entry.fetch(:version)
224
+ publication
225
+ end
226
+ end
227
+
228
+ # @rbs (String uri, String path, Integer? version, String source, bool open,
229
+ # Frontend::SourceDocument? document) -> snapshot
230
+ def snapshot(uri, path, version, source, open, document)
231
+ { uri: uri, path: path, version: version, source: source, open: open, document: document }
232
+ end
233
+
234
+ # @rbs (String path, String uri) -> snapshot
235
+ def open_snapshot(path, uri)
236
+ entry = @snapshots[path]
237
+ return entry if entry&.fetch(:open) && entry.fetch(:uri) == uri
238
+
239
+ raise ProtocolError.new("document is not open: #{uri}", code: -32_602)
240
+ end
241
+
242
+ # @rbs () -> Array[String]
243
+ def empty_paths
244
+ [] #: Array[String]
245
+ end
246
+
247
+ # @rbs (String path) -> void
248
+ def remove_path(path)
249
+ @snapshots.delete(path)
250
+ @document_diagnostics.delete(path)
251
+ remove_root_state(path)
252
+ end
253
+
254
+ # @rbs (String path) -> void
255
+ def remove_root_state(path)
256
+ @roots.delete(path)
257
+ @closures.delete(path)
258
+ @resolutions.delete(path)
259
+ @root_diagnostics.delete(path)
260
+ rebuild_reverse_dependencies
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Owns per-document and per-root diagnostic aggregation for DocumentStore.
6
+ module DocumentStoreDiagnostics
7
+ private
8
+
9
+ # @rbs (String root, Array[String] files, Hash[String, WorkspaceAnalyzer::analyzed_document] analyzed,
10
+ # Array[WorkspaceAnalyzer::lsp_diagnostic] errors) -> void
11
+ def replace_root_diagnostics(root, files, analyzed, errors)
12
+ # @type self: DocumentStore
13
+ owned = {} #: Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]
14
+ files.each { |path| owned[path] = analyzed.dig(path, :diagnostics) || [] }
15
+ errors.each do |diagnostic|
16
+ file = diagnostic.dig("data", "file")
17
+ next unless file
18
+
19
+ existing = owned[file] || []
20
+ duplicate = existing.any? do |entry|
21
+ entry["range"] == diagnostic["range"] && entry["message"] == diagnostic["message"]
22
+ end
23
+ owned[file] = existing + [diagnostic] unless duplicate
24
+ end
25
+ @root_diagnostics[root] = owned
26
+ end
27
+
28
+ # @rbs (String path) -> Array[WorkspaceAnalyzer::lsp_diagnostic]
29
+ def aggregated_diagnostics(path)
30
+ # @type self: DocumentStore
31
+ diagnostics = @document_diagnostics.fetch(path, empty_diagnostics).dup
32
+ @root_diagnostics.each_value do |owned|
33
+ diagnostics.concat(owned.fetch(path, empty_diagnostics))
34
+ end
35
+ diagnostics.uniq
36
+ end
37
+
38
+ # @rbs () -> Array[WorkspaceAnalyzer::lsp_diagnostic]
39
+ def empty_diagnostics
40
+ [] #: Array[WorkspaceAnalyzer::lsp_diagnostic]
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
+ # Validates versions, source bounds, and prospective multi-file workspace edits.
6
+ module DocumentStoreValidation
7
+ # @rbs (Hash[String, String] replacements) -> bool
8
+ def valid_replacements?(replacements)
9
+ # @type self: DocumentStore
10
+ candidate = Frontend::SourceLoader.new
11
+ @snapshots.each_value do |entry|
12
+ candidate.set_overlay(entry.fetch(:path), entry.fetch(:source)) if entry.fetch(:open)
13
+ end
14
+ replacements.each do |path, source|
15
+ candidate.set_overlay(path, source)
16
+ parse_candidate(path, source)
17
+ end
18
+ roots = replacements.keys.flat_map { |path| affected_roots(path) }.uniq
19
+ roots.each { |root| Frontend::Resolver.new(root, mode: :extended, loader: candidate).resolve }
20
+ true
21
+ rescue Ibex::Error, SystemCallError
22
+ false
23
+ end
24
+
25
+ private
26
+
27
+ # @rbs (Integer version) -> void
28
+ def validate_version(version)
29
+ raise ProtocolError.new("document version must be a non-negative integer", code: -32_602) unless
30
+ version.is_a?(Integer) && !version.negative?
31
+ end
32
+
33
+ # @rbs (String source) -> void
34
+ def validate_source(source)
35
+ raise ProtocolError.new("document text must be a string", code: -32_602) unless source.is_a?(String)
36
+ return if source.bytesize <= Limits::MAX_DOCUMENT_BYTES
37
+
38
+ raise ProtocolError.new("document exceeds #{Limits::MAX_DOCUMENT_BYTES} bytes", code: -32_602)
39
+ end
40
+
41
+ # @rbs (String path, String source) -> void
42
+ def parse_candidate(path, source)
43
+ Frontend::Parser.new(source, file: path, mode: :extended).parse_source_document
44
+ end
45
+
46
+ # Resolve disk independently from overlay aliases before removing an open buffer.
47
+ # @rbs (String path, String uri) -> String?
48
+ def disk_path_for_close(path, uri)
49
+ # @type self: DocumentStore
50
+ canonical = loader.disk_path(path)
51
+ return canonical if workspace.root_for(canonical)
52
+
53
+ raise ProtocolError.new("document disk target is outside workspace roots: #{uri}", code: -32_602)
54
+ rescue Errno::ENOENT, Errno::ENOTDIR
55
+ nil
56
+ rescue SystemCallError => e
57
+ raise ProtocolError.new("cannot resolve document disk target #{uri.inspect}: #{e.message}", code: -32_602)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Handles initialize/initialized/shutdown/exit lifecycle transitions.
6
+ module InitializationHandlers
7
+ include RequestSupport
8
+
9
+ private
10
+
11
+ # @rbs (untyped raw_params) -> Hash[String, untyped]
12
+ def initialize_workspace(raw_params)
13
+ raise ProtocolError.new("server is already initialized", code: -32_600) unless @state == :uninitialized
14
+
15
+ params = params_hash(raw_params)
16
+ loader = Frontend::SourceLoader.new
17
+ @workspace = Workspace.new(initialization_roots(params), loader)
18
+ @store = DocumentStore.new(@workspace, loader)
19
+ @state = :initialized
20
+ {
21
+ "capabilities" => capabilities,
22
+ "serverInfo" => { "name" => "ibex", "version" => Ibex::VERSION }
23
+ }
24
+ end
25
+
26
+ # @rbs (untyped params) -> nil
27
+ def initialized_notification(_params)
28
+ require_state!(:initialized)
29
+ @state = :running
30
+ nil
31
+ end
32
+
33
+ # @rbs (untyped params) -> nil
34
+ def shutdown_request(_params)
35
+ require_state!(:running)
36
+ @state = :shutdown
37
+ nil
38
+ end
39
+
40
+ # @rbs (untyped params) -> nil
41
+ def exit_notification(_params)
42
+ @exit_status = @state == :shutdown ? 0 : 1
43
+ @exit_requested = true
44
+ nil
45
+ end
46
+
47
+ # @rbs () -> Hash[String, untyped]
48
+ def capabilities
49
+ {
50
+ "positionEncoding" => "utf-16",
51
+ "textDocumentSync" => { "openClose" => true, "change" => 1, "save" => { "includeText" => true } },
52
+ "definitionProvider" => true,
53
+ "referencesProvider" => true,
54
+ "renameProvider" => { "prepareProvider" => true },
55
+ "hoverProvider" => true,
56
+ "workspace" => { "workspaceFolders" => { "supported" => true, "changeNotifications" => false } }
57
+ }
58
+ end
59
+
60
+ # @rbs (Hash[String, untyped] params) -> Array[String]
61
+ def initialization_roots(params)
62
+ folders = params["workspaceFolders"]
63
+ if folders.is_a?(Array) && !folders.empty?
64
+ return folders.map do |folder|
65
+ raise ProtocolError.new("workspaceFolders entries must be objects", code: -32_602) unless folder.is_a?(Hash)
66
+
67
+ string_member(folder, "uri")
68
+ end
69
+ end
70
+
71
+ root = params["rootUri"]
72
+ return [root] if root.is_a?(String)
73
+
74
+ raise ProtocolError.new("initialize requires rootUri or workspaceFolders", code: -32_602)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Serves symbol navigation, rename, and hover requests.
6
+ module NavigationHandlers
7
+ include RequestSupport
8
+
9
+ private
10
+
11
+ # @rbs (untyped raw_params) -> Array[Hash[String, untyped]]
12
+ def definition(raw_params)
13
+ with_index(raw_params) { |index, path, position| index.definition(path, position) }
14
+ end
15
+
16
+ # @rbs (untyped raw_params) -> Array[Hash[String, untyped]]
17
+ def references(raw_params)
18
+ params = params_hash(raw_params)
19
+ context = hash_member(params, "context")
20
+ include_declaration = context["includeDeclaration"] == true
21
+ with_index(params) do |index, path, position|
22
+ index.references(path, position, include_declaration: include_declaration)
23
+ end
24
+ end
25
+
26
+ # @rbs (untyped raw_params) -> Hash[String, untyped]?
27
+ def prepare_rename(raw_params)
28
+ with_index(raw_params) { |index, path, position| index.prepare_rename(path, position) }
29
+ end
30
+
31
+ # @rbs (untyped raw_params) -> Hash[String, untyped]
32
+ def rename(raw_params)
33
+ params = params_hash(raw_params)
34
+ new_name = string_member(params, "newName")
35
+ with_index(params) { |index, path, position| index.rename(path, position, new_name) }
36
+ end
37
+
38
+ # @rbs (untyped raw_params) -> Hash[String, untyped]?
39
+ def hover(raw_params)
40
+ with_index(raw_params) { |index, path, position| index.hover(path, position) }
41
+ end
42
+
43
+ # @rbs (untyped raw_params) { (SymbolIndex, String, Hash[String, untyped]) -> untyped } -> untyped
44
+ def with_index(raw_params)
45
+ require_running!
46
+ params = params_hash(raw_params)
47
+ document = hash_member(params, "textDocument")
48
+ path = workspace.path(string_member(document, "uri"))
49
+ position = hash_member(params, "position")
50
+ yield SymbolIndex.new(store, path), path, position
51
+ rescue ArgumentError => e
52
+ raise ProtocolError.new(e.message, code: -32_602)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Converts between frontend byte offsets and zero-based LSP UTF-16 positions.
6
+ class PositionCodec
7
+ # @rbs (String source) -> void
8
+ def initialize(source)
9
+ @source = Frontend::SourceEncoding.validated_utf8(source, "(lsp)")
10
+ @line_starts = build_line_starts.freeze #: Array[Integer]
11
+ end
12
+
13
+ # @rbs (Integer byte_offset) -> Hash[String, Integer]
14
+ def position(byte_offset)
15
+ validate_byte_offset(byte_offset)
16
+ prefix = @source.byteslice(0, byte_offset) || ""
17
+ raise ArgumentError, "byte offset is not on a UTF-8 character boundary" unless prefix.valid_encoding?
18
+
19
+ line = (@line_starts.bsearch_index { |start| start > byte_offset } || @line_starts.length) - 1
20
+ line_start = @line_starts.fetch(line)
21
+ line_end = content_end(line)
22
+ raise ArgumentError, "byte offset points inside a line ending" if byte_offset > line_end
23
+
24
+ text = @source.byteslice(line_start, byte_offset - line_start) || ""
25
+ { "line" => line, "character" => utf16_length(text) }
26
+ end
27
+
28
+ # @rbs (Hash[String, untyped] position) -> Integer
29
+ def byte_offset(position)
30
+ line = integer_member(position, "line")
31
+ character = integer_member(position, "character")
32
+ raise ArgumentError, "line and character must be non-negative" if line.negative? || character.negative?
33
+
34
+ line_start = @line_starts[line]
35
+ raise ArgumentError, "line is outside the document" unless line_start
36
+
37
+ text = @source.byteslice(line_start, content_end(line) - line_start) || ""
38
+ line_start + byte_length_at_utf16(text, character)
39
+ end
40
+
41
+ # @rbs (Frontend::SourceSpan span) -> Hash[String, Hash[String, Integer]]
42
+ def range(span)
43
+ { "start" => position(span.start_byte), "end" => position(span.end_byte) }
44
+ end
45
+
46
+ private
47
+
48
+ # @rbs () -> Array[Integer]
49
+ def build_line_starts
50
+ starts = [0]
51
+ offset = 0
52
+ @source.each_char do |character|
53
+ offset += character.bytesize
54
+ starts << offset if character == "\n"
55
+ end
56
+ starts
57
+ end
58
+
59
+ # @rbs (Integer line) -> Integer
60
+ def content_end(line)
61
+ boundary = @line_starts[line + 1] || @source.bytesize
62
+ return boundary unless @line_starts[line + 1]
63
+
64
+ before_newline = boundary - 1
65
+ @source.getbyte(before_newline - 1) == 13 ? before_newline - 1 : before_newline
66
+ end
67
+
68
+ # @rbs (String text) -> Integer
69
+ def utf16_length(text)
70
+ text.each_codepoint.sum { |codepoint| codepoint > 0xFFFF ? 2 : 1 }
71
+ end
72
+
73
+ # @rbs (String text, Integer units) -> Integer
74
+ def byte_length_at_utf16(text, units)
75
+ consumed_units = 0
76
+ consumed_bytes = 0
77
+ text.each_char do |character|
78
+ return consumed_bytes if consumed_units == units
79
+
80
+ width = character.ord > 0xFFFF ? 2 : 1
81
+ if consumed_units + width > units
82
+ raise ArgumentError, "character points into the middle of a UTF-16 surrogate pair"
83
+ end
84
+
85
+ consumed_units += width
86
+ consumed_bytes += character.bytesize
87
+ end
88
+ return consumed_bytes if consumed_units == units
89
+
90
+ raise ArgumentError, "character is outside the document line"
91
+ end
92
+
93
+ # @rbs (Hash[String, untyped] position, String name) -> Integer
94
+ def integer_member(position, name)
95
+ value = position[name]
96
+ raise ArgumentError, "#{name} must be an integer" unless value.is_a?(Integer)
97
+
98
+ value
99
+ end
100
+
101
+ # @rbs (Integer byte_offset) -> void
102
+ def validate_byte_offset(byte_offset)
103
+ return if byte_offset.is_a?(Integer) && byte_offset.between?(0, @source.bytesize)
104
+
105
+ raise ArgumentError, "byte offset is outside the document"
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Resource bounds shared by transport and open-document validation.
6
+ module Limits
7
+ MAX_HEADER_BYTES = 16 * 1024
8
+ MAX_MESSAGE_BYTES = 1024 * 1024
9
+ MAX_DOCUMENT_BYTES = 2 * 1024 * 1024
10
+ MAX_OUTPUT_BYTES = 1024 * 1024
11
+ MAX_ERROR_MESSAGE_CHARACTERS = 1024
12
+ MAX_REQUEST_ID_BYTES = 256
13
+ end
14
+
15
+ # Bounded protocol and workspace failures with their JSON-RPC error code.
16
+ class ProtocolError < Ibex::Error
17
+ attr_reader :code #: Integer
18
+ attr_reader :request_id #: String | Integer | nil
19
+ attr_reader :fatal #: bool
20
+
21
+ # @rbs (String message, ?code: Integer, ?request_id: String | Integer | nil, ?fatal: bool) -> void
22
+ def initialize(message, code: -32_600, request_id: nil, fatal: false)
23
+ normalized = message.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
24
+ bounded = normalized[0, Limits::MAX_ERROR_MESSAGE_CHARACTERS] || ""
25
+ bounded += "…" if bounded.length < normalized.length
26
+ super("(lsp):1:1: #{bounded}")
27
+ @code = code
28
+ @request_id = request_id
29
+ @fatal = fatal
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Dispatches the finite LSP surface to focused lifecycle, document, and navigation handlers.
6
+ module RequestHandlers
7
+ include InitializationHandlers
8
+ include DocumentHandlers
9
+ include NavigationHandlers
10
+ include RequestSupport
11
+
12
+ HANDLERS = {
13
+ "initialize" => :initialize_workspace,
14
+ "initialized" => :initialized_notification,
15
+ "shutdown" => :shutdown_request,
16
+ "exit" => :exit_notification,
17
+ "textDocument/didOpen" => :did_open,
18
+ "textDocument/didChange" => :did_change,
19
+ "textDocument/didSave" => :did_save,
20
+ "textDocument/didClose" => :did_close,
21
+ "textDocument/definition" => :definition,
22
+ "textDocument/references" => :references,
23
+ "textDocument/prepareRename" => :prepare_rename,
24
+ "textDocument/rename" => :rename,
25
+ "textDocument/hover" => :hover,
26
+ "$/cancelRequest" => :cancel_request
27
+ }.freeze #: Hash[String, Symbol]
28
+
29
+ private
30
+
31
+ # @rbs (String method, untyped params) -> untyped
32
+ def dispatch(method, params)
33
+ handler = HANDLERS[method]
34
+ raise ProtocolError.new("method not found: #{method}", code: -32_601) unless handler
35
+
36
+ send(handler, params)
37
+ end
38
+
39
+ # @rbs (untyped params) -> nil
40
+ def cancel_request(_params)
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end