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,236 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "set"
5
+
6
+ module Ibex
7
+ module LALR
8
+ # Builds an LR(0) collection and propagates LALR(1) lookaheads directly
9
+ # over item occurrences. Canonical LR(1) states are never materialized.
10
+ class DirectLookaheads
11
+ AUGMENTED_PRODUCTION = -1 #: Integer
12
+ EMPTY_PRODUCTIONS = Array.new(0).freeze #: Array[IR::Production]
13
+ EMPTY_NODES = Array.new(0).freeze #: Array[lookahead_node]
14
+ private_constant :EMPTY_PRODUCTIONS, :EMPTY_NODES
15
+
16
+ # @rbs @grammar: IR::Grammar
17
+ # @rbs @sets: Analysis::Sets
18
+ # @rbs @productions_by_lhs: Hash[Integer, Array[IR::Production]]
19
+ # @rbs @augmented_rhs: Array[Integer]
20
+ # @rbs @production_rhs: Array[Array[Integer]]
21
+ # @rbs @augmented_item_cores: Array[item_core]
22
+ # @rbs @production_item_cores: Array[Array[item_core]]
23
+ # @rbs @terminal_ids: Array[Integer]
24
+ # @rbs @terminal_masks: Array[Integer]
25
+
26
+ # @rbs (IR::Grammar grammar, Analysis::Sets sets) -> void
27
+ def initialize(grammar, sets)
28
+ @grammar = grammar
29
+ @sets = sets
30
+ @productions_by_lhs = grammar.productions.group_by(&:lhs)
31
+ start = grammar.symbol(grammar.start) || raise(Ibex::Error, "missing start symbol")
32
+ @augmented_rhs = [start.id].freeze
33
+ @production_rhs = grammar.productions.map(&:rhs).freeze
34
+ @augmented_item_cores = item_cores_for(AUGMENTED_PRODUCTION, @augmented_rhs.length)
35
+ @production_item_cores = grammar.productions.map do |production|
36
+ item_cores_for(production.id, production.rhs.length)
37
+ end.freeze
38
+ @terminal_ids = grammar.terminals.map(&:id).freeze
39
+ @terminal_masks = @terminal_ids.map { |id| 1 << id }.freeze
40
+ end
41
+
42
+ # @rbs () -> [Array[packed_items], transitions]
43
+ def build
44
+ states, transitions = lr0_collection
45
+ lookaheads = empty_lookaheads(states)
46
+ propagation = propagation_graph(states, transitions, lookaheads)
47
+ lookaheads.fetch(0).fetch(item_core(AUGMENTED_PRODUCTION, 0)) << 0
48
+ propagate(lookaheads, propagation)
49
+ [lookaheads, transitions]
50
+ end
51
+
52
+ private
53
+
54
+ # @rbs () -> [Array[core_set], transitions]
55
+ def lr0_collection
56
+ seed = Set[item_core(AUGMENTED_PRODUCTION, 0)] #: core_set
57
+ states = [closure(seed)]
58
+ transitions = [] #: transitions
59
+ indexes = { item_key(states.first) => 0 }
60
+ cursor = 0
61
+ while cursor < states.length
62
+ transitions[cursor] = {}
63
+ kernels = shifted_kernels(states.fetch(cursor))
64
+ kernels.keys.sort.each do |symbol_id|
65
+ target = closure(kernels.fetch(symbol_id))
66
+ key = item_key(target)
67
+ target_id = indexes[key] ||= begin
68
+ states << target
69
+ states.length - 1
70
+ end
71
+ transitions.fetch(cursor)[symbol_id] = target_id
72
+ end
73
+ cursor += 1
74
+ end
75
+ [states, transitions]
76
+ end
77
+
78
+ # @rbs (core_set seed) -> core_set
79
+ def closure(seed)
80
+ items = seed.dup
81
+ queue = seed.to_a
82
+ cursor = 0
83
+ while cursor < queue.length
84
+ production_id, dot = queue.fetch(cursor)
85
+ cursor += 1
86
+ symbol = @grammar.symbol_by_id(rhs_for(production_id)[dot])
87
+ next unless symbol&.nonterminal?
88
+
89
+ @productions_by_lhs.fetch(symbol.id, EMPTY_PRODUCTIONS).each do |production|
90
+ item = item_core(production.id, 0)
91
+ queue << item if items.add?(item)
92
+ end
93
+ end
94
+ items
95
+ end
96
+
97
+ # @rbs (core_set items) -> Hash[Integer, core_set]
98
+ def shifted_kernels(items)
99
+ kernels = {} #: Hash[Integer, core_set]
100
+ items.each do |production_id, dot|
101
+ symbol_id = rhs_for(production_id)[dot]
102
+ next unless symbol_id
103
+
104
+ (kernels[symbol_id] ||= Set.new) << item_core(production_id, dot + 1)
105
+ end
106
+ kernels
107
+ end
108
+
109
+ # @rbs (Array[core_set] states) -> Array[packed_items]
110
+ def empty_lookaheads(states)
111
+ states.map do |items|
112
+ items.to_h { |item| [item, Set.new] } #: packed_items
113
+ end
114
+ end
115
+
116
+ # @rbs (Array[core_set] states, transitions transitions, Array[packed_items] lookaheads) ->
117
+ # Hash[lookahead_node, Array[lookahead_node]]
118
+ def propagation_graph(states, transitions, lookaheads)
119
+ edges = Hash.new { |hash, key| hash[key] = [] } #: Hash[lookahead_node, Array[lookahead_node]]
120
+ states.each_with_index do |items, state_id|
121
+ items.each do |production_id, dot|
122
+ add_transition_edge(edges, transitions, state_id, production_id, dot)
123
+ add_closure_relations(edges, lookaheads, state_id, production_id, dot)
124
+ end
125
+ end
126
+ edges.each_value(&:uniq!)
127
+ edges
128
+ end
129
+
130
+ # @rbs (Hash[lookahead_node, Array[lookahead_node]] edges, transitions transitions,
131
+ # Integer state_id, Integer production_id, Integer dot) -> void
132
+ def add_transition_edge(edges, transitions, state_id, production_id, dot)
133
+ symbol_id = rhs_for(production_id)[dot]
134
+ return unless symbol_id
135
+
136
+ target_state = transitions.fetch(state_id).fetch(symbol_id)
137
+ source = [state_id, production_id, dot] #: lookahead_node
138
+ target = [target_state, production_id, dot + 1] #: lookahead_node
139
+ edges[source] << target
140
+ end
141
+
142
+ # @rbs (Hash[lookahead_node, Array[lookahead_node]] edges, Array[packed_items] lookaheads,
143
+ # Integer state_id, Integer production_id, Integer dot) -> void
144
+ def add_closure_relations(edges, lookaheads, state_id, production_id, dot)
145
+ rhs = rhs_for(production_id)
146
+ symbol = @grammar.symbol_by_id(rhs[dot])
147
+ return unless symbol&.nonterminal?
148
+
149
+ suffix = rhs.drop(dot + 1)
150
+ spontaneous = terminal_ids(@sets.first_of_sequence(suffix))
151
+ source = [state_id, production_id, dot] #: lookahead_node
152
+ @productions_by_lhs.fetch(symbol.id, EMPTY_PRODUCTIONS).each do |production|
153
+ target_item = item_core(production.id, 0)
154
+ lookaheads.fetch(state_id).fetch(target_item).merge(spontaneous)
155
+ if @sets.sequence_nullable?(suffix)
156
+ target = [state_id, production.id, 0] #: lookahead_node
157
+ edges[source] << target
158
+ end
159
+ end
160
+ end
161
+
162
+ # @rbs (Array[packed_items] lookaheads, Hash[lookahead_node, Array[lookahead_node]] edges) -> void
163
+ def propagate(lookaheads, edges)
164
+ queue = lookaheads.each_with_index.flat_map do |items, state_id|
165
+ items.filter_map do |(production_id, dot), tokens|
166
+ next if tokens.empty?
167
+
168
+ [state_id, production_id, dot] #: lookahead_node
169
+ end
170
+ end #: Array[lookahead_node]
171
+ queued = queue.to_h { |node| [node, true] } #: Hash[lookahead_node, bool]
172
+ cursor = 0
173
+ while cursor < queue.length
174
+ source = queue.fetch(cursor)
175
+ cursor += 1
176
+ queued.delete(source)
177
+ source_set = node_set(lookaheads, source)
178
+ edges.fetch(source, EMPTY_NODES).each do |target|
179
+ target_set = node_set(lookaheads, target)
180
+ previous_size = target_set.size
181
+ target_set.merge(source_set)
182
+ next if target_set.size == previous_size || queued[target]
183
+
184
+ queue << target
185
+ queued[target] = true
186
+ end
187
+ end
188
+ end
189
+
190
+ # @rbs (Array[packed_items] lookaheads, lookahead_node node) -> Set[Integer]
191
+ def node_set(lookaheads, node)
192
+ state_id, production_id, dot = node
193
+ lookaheads.fetch(state_id).fetch(item_core(production_id, dot))
194
+ end
195
+
196
+ # @rbs (Integer bits) -> Array[Integer]
197
+ def terminal_ids(bits)
198
+ selected = [] #: Array[Integer]
199
+ index = 0
200
+ while index < @terminal_ids.length
201
+ selected << @terminal_ids[index] if bits.anybits?(@terminal_masks[index])
202
+ index += 1
203
+ end
204
+ selected
205
+ end
206
+
207
+ # @rbs (Integer production_id) -> Array[Integer]
208
+ def rhs_for(production_id)
209
+ return @augmented_rhs if production_id == AUGMENTED_PRODUCTION
210
+
211
+ @production_rhs.fetch(production_id)
212
+ end
213
+
214
+ # @rbs (Integer production_id, Integer rhs_length) -> Array[item_core]
215
+ def item_cores_for(production_id, rhs_length)
216
+ Array.new(rhs_length + 1) do |dot|
217
+ [production_id, dot].freeze #: item_core
218
+ end.freeze
219
+ end
220
+
221
+ # @rbs (Integer production_id, Integer dot) -> item_core
222
+ def item_core(production_id, dot)
223
+ raise IndexError, "invalid item dot: #{dot}" if dot.negative?
224
+ return @augmented_item_cores.fetch(dot) if production_id == AUGMENTED_PRODUCTION
225
+ raise IndexError, "invalid production id: #{production_id}" if production_id.negative?
226
+
227
+ @production_item_cores.fetch(production_id).fetch(dot)
228
+ end
229
+
230
+ # @rbs (core_set items) -> Array[item_core]
231
+ def item_key(items)
232
+ items.to_a.sort
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "set"
5
+
6
+ module Ibex
7
+ module LALR
8
+ # Deterministically merges compatible canonical LR(1) states and splits
9
+ # partitions until their outgoing transitions are congruent.
10
+ class IELRPartition
11
+ AUGMENTED_PRODUCTION = -1 #: Integer
12
+
13
+ # @rbs @grammar: IR::Grammar
14
+ # @rbs @states: Array[item_set]
15
+ # @rbs @transitions: transitions
16
+ # @rbs @contributions: Array[Hash[Integer, Set[Array[untyped]]]]
17
+
18
+ # @rbs (IR::Grammar grammar, Array[item_set] states, transitions transitions) -> void
19
+ def initialize(grammar, states, transitions)
20
+ @grammar = grammar
21
+ @states = states
22
+ @transitions = transitions
23
+ @contributions = Array.new(states.length) { |state_id| action_contributions(state_id) }
24
+ end
25
+
26
+ # @rbs () -> [Array[packed_items], transitions]
27
+ def build
28
+ partitions = refine_transitions(initial_partitions)
29
+ indexes = partition_indexes(partitions)
30
+ items = partitions.map { |members| merge_items(members) }
31
+ transitions = partitions.map do |members|
32
+ @transitions.fetch(members.first).to_h do |symbol_id, target|
33
+ [symbol_id, indexes.fetch(target)]
34
+ end
35
+ end
36
+ [items, transitions]
37
+ end
38
+
39
+ private
40
+
41
+ # @rbs () -> Array[state_partition]
42
+ def initial_partitions
43
+ cores = {} #: Hash[Array[item_core], Array[Integer]]
44
+ @states.each_with_index do |items, state_id|
45
+ cores[core_key(items)] ||= []
46
+ cores.fetch(core_key(items)) << state_id
47
+ end
48
+ cores.values.flat_map { |members| compatible_partitions(members) }
49
+ end
50
+
51
+ # @rbs (Array[Integer] members) -> Array[state_partition]
52
+ def compatible_partitions(members)
53
+ partitions = [] #: Array[state_partition]
54
+ members.each do |state_id|
55
+ partition = partitions.find { |candidate| compatible?(candidate + [state_id]) }
56
+ partition ? partition << state_id : partitions << [state_id]
57
+ end
58
+ partitions
59
+ end
60
+
61
+ # A canonical member may gain an action only where it previously had no
62
+ # action. Every nonempty member cell must equal the merged cell.
63
+ # @rbs (state_partition members) -> bool
64
+ def compatible?(members)
65
+ merged = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[Array[untyped]]]
66
+ members.each do |state_id|
67
+ @contributions.fetch(state_id).each { |token_id, actions| merged[token_id].merge(actions) }
68
+ end
69
+ merged.all? do |token_id, actions|
70
+ members.all? do |state_id|
71
+ member_actions = @contributions.fetch(state_id)[token_id]
72
+ member_actions.nil? || member_actions.empty? || member_actions == actions
73
+ end
74
+ end
75
+ end
76
+
77
+ # @rbs (Array[state_partition] partitions) -> Array[state_partition]
78
+ def refine_transitions(partitions)
79
+ loop do
80
+ indexes = partition_indexes(partitions)
81
+ refined = partitions.flat_map do |members|
82
+ members.group_by { |state_id| transition_signature(state_id, indexes) }.values
83
+ end
84
+ return partitions if refined == partitions
85
+
86
+ partitions = refined
87
+ end
88
+ end
89
+
90
+ # @rbs (Integer state_id, Hash[Integer, Integer] indexes) -> Array[[Integer, Integer]]
91
+ def transition_signature(state_id, indexes)
92
+ @transitions.fetch(state_id).map do |symbol_id, target|
93
+ [symbol_id, indexes.fetch(target)] #: [Integer, Integer]
94
+ end.sort
95
+ end
96
+
97
+ # @rbs (Array[state_partition] partitions) -> Hash[Integer, Integer]
98
+ def partition_indexes(partitions)
99
+ indexes = {} #: Hash[Integer, Integer]
100
+ partitions.each_with_index do |members, partition_id|
101
+ members.each { |state_id| indexes[state_id] = partition_id }
102
+ end
103
+ indexes
104
+ end
105
+
106
+ # @rbs (state_partition members) -> packed_items
107
+ def merge_items(members)
108
+ merged = Hash.new { |hash, key| hash[key] = Set.new } #: packed_items
109
+ members.each do |state_id|
110
+ @states.fetch(state_id).each do |production_id, dot, lookahead|
111
+ merged[[production_id, dot]] << lookahead
112
+ end
113
+ end
114
+ merged
115
+ end
116
+
117
+ # @rbs (Integer state_id) -> Hash[Integer, Set[Array[untyped]]]
118
+ def action_contributions(state_id)
119
+ actions = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[Array[untyped]]]
120
+ @transitions.fetch(state_id).each_key do |symbol_id|
121
+ symbol = @grammar.symbol_by_id(symbol_id)
122
+ actions[symbol_id] << [:shift] if symbol&.terminal?
123
+ end
124
+ @states.fetch(state_id).each do |production_id, dot, lookahead|
125
+ next unless dot == rhs_for(production_id).length
126
+
127
+ action = production_id.negative? ? [:accept] : [:reduce, production_id]
128
+ actions[lookahead] << action
129
+ end
130
+ actions
131
+ end
132
+
133
+ # @rbs (Integer production_id) -> Array[Integer]
134
+ def rhs_for(production_id)
135
+ if production_id.negative?
136
+ name = @grammar.starts.fetch(-production_id - 1)
137
+ start = @grammar.symbol(name) || raise(Ibex::Error, "missing start symbol #{name}")
138
+ return [start.id]
139
+ end
140
+
141
+ @grammar.productions.fetch(production_id).rhs
142
+ end
143
+
144
+ # @rbs (item_set items) -> Array[item_core]
145
+ def core_key(items)
146
+ items.map do |production_id, dot, _lookahead|
147
+ [production_id, dot] #: item_core
148
+ end.uniq.sort
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LALR
5
+ # Delays syntax-error detection by filling only otherwise erroneous table
6
+ # cells with one uniquely highest-priority declared reduction.
7
+ module OnErrorReductions
8
+ # @rbs (IR::Grammar grammar, Array[IR::AutomatonState] states) -> Array[IR::AutomatonState]
9
+ def apply(grammar, states)
10
+ priorities = reduction_priorities(grammar)
11
+ return states if priorities.empty?
12
+
13
+ terminal_ids = grammar.terminals.reject { |terminal| terminal.name == "error" }.map(&:id)
14
+ states.map { |state| apply_state(grammar, state, priorities, terminal_ids) }
15
+ end
16
+ module_function :apply
17
+
18
+ # @rbs (IR::Grammar grammar) -> Hash[Integer, Integer]
19
+ def reduction_priorities(grammar)
20
+ priorities = {} #: Hash[Integer, Integer]
21
+ grammar.recovery[:on_error_reduce].each_with_index do |names, priority|
22
+ names.each do |name|
23
+ symbol = grammar.symbol(name) || raise(Ibex::Error, "missing on-error reduction symbol #{name}")
24
+ priorities[symbol.id] = priority
25
+ end
26
+ end
27
+ priorities
28
+ end
29
+
30
+ # @rbs (IR::Grammar grammar, IR::AutomatonState state, Hash[Integer, Integer] priorities,
31
+ # Array[Integer] terminal_ids) -> IR::AutomatonState
32
+ def apply_state(grammar, state, priorities, terminal_ids)
33
+ production_id = selected_production(grammar, state, priorities)
34
+ return state unless production_id
35
+
36
+ reduction = { type: :reduce, production: production_id } #: IR::reduce_action
37
+ actions = state.actions.dup
38
+ terminal_ids.each do |token_id|
39
+ action = actions[token_id]
40
+ actions[token_id] = reduction if action.nil? || action[:type] == :error
41
+ end
42
+ IR::AutomatonState.new(
43
+ id: state.id, items: state.items, transitions: state.transitions,
44
+ actions: actions, gotos: state.gotos, default_action: state.default_action,
45
+ conflicts: state.conflicts
46
+ )
47
+ end
48
+
49
+ # @rbs (IR::Grammar grammar, IR::AutomatonState state, Hash[Integer, Integer] priorities) -> Integer?
50
+ def selected_production(grammar, state, priorities)
51
+ candidates = state.items.filter_map do |item|
52
+ next if item.production.negative?
53
+
54
+ production = grammar.productions.fetch(item.production)
55
+ next unless item.dot == production.rhs.length
56
+
57
+ priority = priorities[production.lhs]
58
+ [priority, production.id] if priority
59
+ end.uniq
60
+ highest = candidates.filter_map(&:first).max
61
+ return unless highest
62
+
63
+ productions = candidates.filter_map { |priority, production| production if priority == highest }.uniq
64
+ productions.one? ? productions.fetch(0) : nil
65
+ end
66
+
67
+ module_function :reduction_priorities, :apply_state, :selected_production
68
+
69
+ class << self
70
+ private :reduction_priorities, :apply_state, :selected_production
71
+ end
72
+ end
73
+ end
74
+ end
data/lib/ibex/lalr.rb CHANGED
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "lalr/conflict"
4
+ require_relative "lalr/on_error_reductions"
4
5
  require_relative "lalr/default_reductions"
6
+ require_relative "lalr/build_metrics"
7
+ require_relative "lalr/direct_lookaheads"
8
+ require_relative "lalr/ielr_partition"
5
9
  require_relative "lalr/builder"
6
10
  require_relative "lalr/conflict_search_limits"
7
11
  require_relative "lalr/conflict_search"
@@ -12,9 +16,12 @@ module Ibex
12
16
  # @rbs!
13
17
  # type lr_item = [Integer, Integer, Integer]
14
18
  # type item_core = [Integer, Integer]
19
+ # type core_set = Set[item_core]
15
20
  # type item_set = Set[lr_item]
16
21
  # type packed_items = Hash[item_core, Set[Integer]]
17
22
  # type transitions = Array[Hash[Integer, Integer]]
23
+ # type lookahead_node = [Integer, Integer, Integer]
24
+ # type state_partition = Array[Integer]
18
25
  # type derivation_node = Hash[Symbol, untyped]
19
26
  # type search_status = :conflict | :shifted | :accepted
20
27
  # type search_entry = [search_status, ConflictSearch::Configuration]
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # Immutable source range suitable for lexer tokens and parser diagnostics.
6
+ #
7
+ # Coordinates are one-based. Byte offsets are optional, zero-based, and
8
+ # half-open. Keeping both forms lets Unicode-aware lexers report human
9
+ # columns without losing an exact source slice.
10
+ class Location
11
+ attr_reader :file #: String?
12
+ attr_reader :line #: Integer
13
+ attr_reader :column #: Integer
14
+ attr_reader :end_line #: Integer
15
+ attr_reader :end_column #: Integer
16
+ attr_reader :start_byte #: Integer?
17
+ attr_reader :end_byte #: Integer?
18
+ attr_reader :source_line #: String?
19
+
20
+ # @rbs (?file: String?, line: Integer, column: Integer, ?end_line: Integer?,
21
+ # ?end_column: Integer?, ?start_byte: Integer?, ?end_byte: Integer?, ?source_line: String?) -> void
22
+ def initialize(line:, column:, file: nil, end_line: nil, end_column: nil,
23
+ start_byte: nil, end_byte: nil, source_line: nil)
24
+ @file = file&.dup&.freeze
25
+ @line = positive_coordinate(:line, line)
26
+ @column = positive_coordinate(:column, column)
27
+ @end_line = positive_coordinate(:end_line, end_line || line)
28
+ @end_column = positive_coordinate(:end_column, end_column || column)
29
+ @start_byte = optional_offset(:start_byte, start_byte)
30
+ @end_byte = optional_offset(:end_byte, end_byte)
31
+ @source_line = source_line&.dup&.freeze
32
+ validate_order!
33
+ freeze
34
+ end
35
+
36
+ # Return the smallest range covering both locations.
37
+ # @rbs (Location other) -> Location
38
+ def join(other)
39
+ raise ArgumentError, "location files differ" unless file == other.file
40
+
41
+ first = before_or_equal?(self, other) ? self : other
42
+ ending = before_or_equal_end?(self, other) ? other : self
43
+ self.class.new(
44
+ file: file, line: first.line, column: first.column,
45
+ end_line: ending.end_line, end_column: ending.end_column,
46
+ start_byte: joined_start_byte(other), end_byte: joined_end_byte(other),
47
+ source_line: first.source_line
48
+ )
49
+ end
50
+
51
+ # Fold a nonempty collection into one covering range.
52
+ # @rbs (Enumerable[Location] locations) -> Location
53
+ def self.join(locations)
54
+ values = locations.to_a
55
+ first = values.shift || raise(ArgumentError, "locations must not be empty")
56
+ values.reduce(first) { |combined, location| combined.join(location) }
57
+ end
58
+
59
+ # @rbs () -> bool
60
+ def empty?
61
+ @line == @end_line && @column == @end_column &&
62
+ (@start_byte.nil? || @end_byte.nil? || @start_byte == @end_byte)
63
+ end
64
+
65
+ # @rbs () -> Hash[Symbol, untyped]
66
+ def to_h
67
+ {
68
+ file: @file, line: @line, column: @column,
69
+ end_line: @end_line, end_column: @end_column,
70
+ start_byte: @start_byte, end_byte: @end_byte
71
+ }.compact
72
+ end
73
+
74
+ private
75
+
76
+ # @rbs (Symbol name, untyped value) -> Integer
77
+ def positive_coordinate(name, value)
78
+ return value if value.is_a?(Integer) && value.positive?
79
+
80
+ raise ArgumentError, "#{name} must be a positive Integer"
81
+ end
82
+
83
+ # @rbs (Symbol name, untyped value) -> Integer?
84
+ def optional_offset(name, value)
85
+ return if value.nil?
86
+ return value if value.is_a?(Integer) && value >= 0
87
+
88
+ raise ArgumentError, "#{name} must be a nonnegative Integer or nil"
89
+ end
90
+
91
+ # @rbs () -> void
92
+ def validate_order!
93
+ if @end_line < @line || (@end_line == @line && @end_column < @column)
94
+ raise ArgumentError, "end position precedes start position"
95
+ end
96
+
97
+ start_byte = @start_byte
98
+ end_byte = @end_byte
99
+ return unless start_byte && end_byte && end_byte < start_byte
100
+
101
+ raise ArgumentError, "end_byte precedes start_byte"
102
+ end
103
+
104
+ # @rbs (Location left, Location right) -> bool
105
+ def before_or_equal?(left, right)
106
+ left.line < right.line || (left.line == right.line && left.column <= right.column)
107
+ end
108
+
109
+ # @rbs (Location left, Location right) -> bool
110
+ def before_or_equal_end?(left, right)
111
+ left.end_line < right.end_line ||
112
+ (left.end_line == right.end_line && left.end_column <= right.end_column)
113
+ end
114
+
115
+ # @rbs (Location other) -> Integer?
116
+ def joined_start_byte(other)
117
+ return unless @start_byte && other.start_byte
118
+
119
+ [@start_byte, other.start_byte].min
120
+ end
121
+
122
+ # @rbs (Location other) -> Integer?
123
+ def joined_end_byte(other)
124
+ return unless @end_byte && other.end_byte
125
+
126
+ [@end_byte, other.end_byte].max
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module LSP
5
+ # Applies full-text LSP document notifications to the overlay-backed store.
6
+ module DocumentHandlers
7
+ include RequestSupport
8
+
9
+ private
10
+
11
+ # @rbs (untyped raw_params) -> nil
12
+ def did_open(raw_params)
13
+ require_running!
14
+ params = params_hash(raw_params)
15
+ document = hash_member(params, "textDocument")
16
+ publications = store.open(
17
+ string_member(document, "uri"), integer_member(document, "version"), string_member(document, "text")
18
+ )
19
+ publish(publications)
20
+ nil
21
+ end
22
+
23
+ # @rbs (untyped raw_params) -> nil
24
+ def did_change(raw_params)
25
+ require_running!
26
+ params = params_hash(raw_params)
27
+ document = hash_member(params, "textDocument")
28
+ changes = params["contentChanges"]
29
+ unless changes.is_a?(Array) && changes.one? && changes.first.is_a?(Hash) &&
30
+ !changes.first.key?("range") && !changes.first.key?("rangeLength")
31
+ raise ProtocolError.new("full sync requires exactly one range-free content change", code: -32_602)
32
+ end
33
+
34
+ source = string_member(changes.fetch(0), "text")
35
+ publications = store.change(
36
+ string_member(document, "uri"), integer_member(document, "version"), source
37
+ )
38
+ publish(publications)
39
+ nil
40
+ end
41
+
42
+ # @rbs (untyped raw_params) -> nil
43
+ def did_save(raw_params)
44
+ require_running!
45
+ params = params_hash(raw_params)
46
+ document = hash_member(params, "textDocument")
47
+ source = params["text"]
48
+ unless source.nil? || source.is_a?(String)
49
+ raise ProtocolError.new("saved document text must be a string", code: -32_602)
50
+ end
51
+
52
+ publish(store.save(string_member(document, "uri"), source: source))
53
+ nil
54
+ end
55
+
56
+ # @rbs (untyped raw_params) -> nil
57
+ def did_close(raw_params)
58
+ require_running!
59
+ params = params_hash(raw_params)
60
+ document = hash_member(params, "textDocument")
61
+ publish(store.close(string_member(document, "uri")))
62
+ nil
63
+ end
64
+ end
65
+ end
66
+ end