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,188 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Coverage
6
+ # Strictly turns one or more complete parse sessions into a coverage report.
7
+ class Collector
8
+ # @rbs @grammar_digest: String?
9
+ # @rbs @table_format_version: Integer?
10
+ # @rbs @state_count: Integer?
11
+ # @rbs @production_count: Integer?
12
+ # @rbs @sessions: Integer
13
+ # @rbs @event_count: Integer
14
+ # @rbs @state_hits: Hash[Integer, Integer]
15
+ # @rbs @production_hits: Hash[Integer, Integer]
16
+ # @rbs @in_session: bool
17
+ # @rbs @expected_sequence: Integer
18
+
19
+ # @rbs () -> void
20
+ def initialize
21
+ @grammar_digest = nil
22
+ @table_format_version = nil
23
+ @state_count = nil
24
+ @production_count = nil
25
+ @sessions = 0
26
+ @event_count = 0
27
+ @state_hits = Hash.new(0)
28
+ @production_hits = Hash.new(0)
29
+ @in_session = false
30
+ @expected_sequence = 1
31
+ end
32
+
33
+ # @rbs (String path) -> Report
34
+ def self.collect_file(path)
35
+ collector = new
36
+ EventStream.each_file(path) do |document, line|
37
+ collector.consume(document, source: path, line: line)
38
+ end
39
+ collector.finish(source: path)
40
+ end
41
+
42
+ # @rbs (Hash[String, untyped] document, source: String, line: Integer) -> void
43
+ def consume(document, source:, line:)
44
+ event = document.fetch("event")
45
+ sequence = document.fetch("sequence")
46
+ if event == "start"
47
+ start_session(document.fetch("data"), sequence, source, line)
48
+ else
49
+ consume_session_event(event, document.fetch("data"), sequence, source, line)
50
+ end
51
+ @event_count += 1
52
+ end
53
+
54
+ # @rbs (source: String) -> Report
55
+ def finish(source:)
56
+ invalid(source, 1, "event stream ended before accept or reject") if @in_session
57
+ invalid(source, 1, "event stream does not contain a parse session") if @sessions.zero?
58
+ Report.new(
59
+ grammar_digest: required_metadata(@grammar_digest),
60
+ table_format_version: required_metadata(@table_format_version),
61
+ state_count: required_metadata(@state_count),
62
+ production_count: required_metadata(@production_count),
63
+ sessions: @sessions,
64
+ event_count: @event_count,
65
+ state_hits: @state_hits,
66
+ production_hits: @production_hits
67
+ )
68
+ rescue ArgumentError => e
69
+ raise Ibex::Error, "#{source}:1:1: #{e.message}"
70
+ end
71
+
72
+ private
73
+
74
+ # @rbs (Hash[untyped, untyped] data, untyped sequence, String source, Integer line) -> void
75
+ def start_session(data, sequence, source, line)
76
+ invalid(source, line, "new start event before prior session ended") if @in_session
77
+ invalid(source, line, "start event sequence must be 1") unless sequence == 1
78
+
79
+ metadata, initial_state = session_metadata(data, source, line)
80
+ establish_metadata(metadata, source, line)
81
+ validate_metadata!(source, line)
82
+ hit_state(initial_state, source, line)
83
+ @sessions += 1
84
+ @in_session = true
85
+ @expected_sequence = 2
86
+ end
87
+
88
+ # @rbs (String event, Hash[untyped, untyped] data, untyped sequence, String source, Integer line) -> void
89
+ def consume_session_event(event, data, sequence, source, line)
90
+ invalid(source, line, "event appears outside a parse session") unless @in_session
91
+ unless sequence == @expected_sequence
92
+ invalid(source, line, "expected event sequence #{@expected_sequence}, got #{sequence.inspect}")
93
+ end
94
+
95
+ case event
96
+ when "shift", "recover" then hit_state(data["state"], source, line)
97
+ when "reduce"
98
+ hit_production(data["production_id"], source, line)
99
+ hit_state(data["goto_state"], source, line)
100
+ end
101
+ if %w[accept reject].include?(event)
102
+ @in_session = false
103
+ @expected_sequence = 1
104
+ else
105
+ @expected_sequence += 1
106
+ end
107
+ end
108
+
109
+ # @rbs (Hash[untyped, untyped] data, String source, Integer line)
110
+ # -> [[String, Integer, Integer, Integer], Integer]
111
+ def session_metadata(data, source, line)
112
+ digest = data["grammar_digest"]
113
+ format = data["table_format_version"]
114
+ states = data["state_count"]
115
+ productions = data["production_count"]
116
+ initial_state = data["initial_state"]
117
+ unless digest.is_a?(String) && format.is_a?(Integer) && states.is_a?(Integer) &&
118
+ productions.is_a?(Integer) && initial_state.is_a?(Integer)
119
+ invalid(source, line, "start event lacks generated parser coverage metadata")
120
+ end
121
+ [[digest, format, states, productions], initial_state]
122
+ end
123
+
124
+ # @rbs ([String, Integer, Integer, Integer] metadata, String source, Integer line) -> void
125
+ def establish_metadata(metadata, source, line)
126
+ unless @grammar_digest
127
+ @grammar_digest, @table_format_version, @state_count, @production_count = metadata
128
+ return
129
+ end
130
+
131
+ expected = [@grammar_digest, @table_format_version, @state_count, @production_count]
132
+ invalid(source, line, "parse sessions use different parser metadata") unless metadata == expected
133
+ end
134
+
135
+ # @rbs (String source, Integer line) -> void
136
+ def validate_metadata!(source, line)
137
+ Report.new(
138
+ grammar_digest: required_metadata(@grammar_digest),
139
+ table_format_version: required_metadata(@table_format_version),
140
+ state_count: required_metadata(@state_count),
141
+ production_count: required_metadata(@production_count),
142
+ sessions: 1,
143
+ event_count: 1,
144
+ state_hits: {},
145
+ production_hits: {}
146
+ )
147
+ rescue ArgumentError => e
148
+ invalid(source, line, e.message)
149
+ end
150
+
151
+ # @rbs (untyped id, String source, Integer line) -> void
152
+ def hit_state(id, source, line)
153
+ total = required_metadata(@state_count)
154
+ invalid(source, line, "state id #{id.inspect} is outside 0...#{total}") unless valid_id?(id, total)
155
+ increment(@state_hits, id, "state", source, line)
156
+ end
157
+
158
+ # @rbs (untyped id, String source, Integer line) -> void
159
+ def hit_production(id, source, line)
160
+ total = required_metadata(@production_count)
161
+ invalid(source, line, "production id #{id.inspect} is outside 0...#{total}") unless valid_id?(id, total)
162
+ increment(@production_hits, id, "production", source, line)
163
+ end
164
+
165
+ # @rbs (untyped id, Integer total) -> bool
166
+ def valid_id?(id, total)
167
+ id.is_a?(Integer) && id >= 0 && id < total
168
+ end
169
+
170
+ # @rbs (Hash[Integer, Integer] hits, Integer id, String kind, String source, Integer line) -> void
171
+ def increment(hits, id, kind, source, line)
172
+ count = hits[id] + 1
173
+ invalid(source, line, "#{kind} hit count exceeds the supported count") if count > Report::MAX_COUNT
174
+ hits[id] = count
175
+ end
176
+
177
+ # @rbs [T] (T? value) -> T
178
+ def required_metadata(value)
179
+ value || raise(ArgumentError, "coverage metadata is unavailable")
180
+ end
181
+
182
+ # @rbs (String source, Integer line, String message) -> bot
183
+ def invalid(source, line, message)
184
+ raise Ibex::Error, "#{source}:#{line}:1: #{message}"
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require_relative "../error"
6
+ require_relative "../runtime/event"
7
+ require_relative "runtime_event_validator"
8
+
9
+ module Ibex
10
+ module Coverage
11
+ # Bounded reader for the public runtime-event JSON Lines protocol.
12
+ module EventStream
13
+ # @rbs!
14
+ # interface _CoverageEventInput
15
+ # def gets: (Integer) -> String?
16
+ # end
17
+
18
+ MAX_LINE_BYTES = 1_048_576 #: Integer
19
+ MAX_NESTING = 32 #: Integer
20
+ ROOT_KEYS = %w[data event ibex_runtime_event schema_version sequence].freeze #: Array[String]
21
+ EVENT_TYPES = %w[start shift reduce error recover discard accept reject].freeze #: Array[String]
22
+
23
+ class << self
24
+ # @rbs (String path) { (Hash[String, untyped], Integer) -> void } -> void
25
+ def each_file(path, &block)
26
+ File.open(path, "rb") { |input| each_io(input, source: path, &block) }
27
+ end
28
+
29
+ # @rbs (_CoverageEventInput input, source: String)
30
+ # { (Hash[String, untyped], Integer) -> void } -> void
31
+ def each_io(input, source:, &block)
32
+ line_number = 0
33
+ while (line = input.gets(MAX_LINE_BYTES + 1))
34
+ line_number += 1
35
+ reject_oversized_line(line, source, line_number)
36
+ document = parse_line(line, source, line_number)
37
+ validate_envelope(document, source, line_number)
38
+ block.call(document, line_number)
39
+ end
40
+ raise_invalid(source, 1, "event stream is empty") if line_number.zero?
41
+ end
42
+
43
+ private
44
+
45
+ # @rbs (String line, String source, Integer line_number) -> void
46
+ def reject_oversized_line(line, source, line_number)
47
+ return if line.bytesize <= MAX_LINE_BYTES
48
+
49
+ raise_invalid(source, line_number, "event line exceeds #{MAX_LINE_BYTES} bytes")
50
+ end
51
+
52
+ # @rbs (String line, String source, Integer line_number) -> Hash[String, untyped]
53
+ def parse_line(line, source, line_number)
54
+ text = line.dup.force_encoding(Encoding::UTF_8)
55
+ raise_invalid(source, line_number, "event line is not valid UTF-8") unless text.valid_encoding?
56
+
57
+ value = JSON.parse(text, max_nesting: MAX_NESTING, allow_nan: false)
58
+ return value if value.is_a?(Hash) && value.keys.all?(String)
59
+
60
+ raise_invalid(source, line_number, "event line must be a JSON object")
61
+ rescue JSON::ParserError => e
62
+ raise_invalid(source, line_number, "invalid event JSON: #{e.message}")
63
+ end
64
+
65
+ # @rbs (Hash[String, untyped] document, String source, Integer line_number) -> void
66
+ def validate_envelope(document, source, line_number)
67
+ unless document.keys.sort == ROOT_KEYS
68
+ raise_invalid(source, line_number, "event object has unknown or missing fields")
69
+ end
70
+ unless document["ibex_runtime_event"] == Runtime::Event::IDENTIFIER &&
71
+ document["schema_version"] == Runtime::Event::SCHEMA_VERSION
72
+ raise_invalid(source, line_number, "unsupported runtime event schema")
73
+ end
74
+ sequence = document["sequence"]
75
+ raise_invalid(source, line_number, "event sequence must be positive") unless positive_integer?(sequence)
76
+ unless EVENT_TYPES.include?(document["event"])
77
+ raise_invalid(source, line_number, "unknown runtime event type")
78
+ end
79
+ raise_invalid(source, line_number, "event data must be an object") unless document["data"].is_a?(Hash)
80
+ return if RuntimeEventValidator.valid?(document)
81
+
82
+ raise_invalid(source, line_number, "event data does not match the runtime-event v1 schema")
83
+ end
84
+
85
+ # @rbs (untyped value) -> bool
86
+ def positive_integer?(value)
87
+ value.is_a?(Integer) && value.positive?
88
+ end
89
+
90
+ # @rbs (String source, Integer line_number, String message) -> bot
91
+ def raise_invalid(source, line_number, message)
92
+ raise Ibex::Error, "#{source}:#{line_number}:1: #{message}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require_relative "../error"
6
+
7
+ module Ibex
8
+ module Coverage
9
+ # Versioned, mergeable runtime coverage result.
10
+ class Report
11
+ IDENTIFIER = "runtime-coverage" #: String
12
+ SCHEMA_VERSION = 1 #: Integer
13
+ MAX_DOCUMENT_BYTES = 16_777_216 #: Integer
14
+ MAX_TOTAL = 1_000_000 #: Integer
15
+ MAX_COUNT = 9_223_372_036_854_775_807 #: Integer
16
+ ROOT_KEYS = %w[events grammar_digest ibex_coverage production_hits schema_version sessions state_hits
17
+ table_format_version totals].freeze #: Array[String]
18
+ TOTAL_KEYS = %w[productions states].freeze #: Array[String]
19
+ HIT_KEYS = %w[count id].freeze #: Array[String]
20
+
21
+ attr_reader :grammar_digest #: String
22
+ attr_reader :table_format_version #: Integer
23
+ attr_reader :state_count #: Integer
24
+ attr_reader :production_count #: Integer
25
+ attr_reader :sessions #: Integer
26
+ attr_reader :event_count #: Integer
27
+ attr_reader :state_hits #: Hash[Integer, Integer]
28
+ attr_reader :production_hits #: Hash[Integer, Integer]
29
+
30
+ # rubocop:disable Layout/LineLength
31
+ # @rbs (grammar_digest: String, table_format_version: Integer, state_count: Integer, production_count: Integer, sessions: Integer, event_count: Integer, state_hits: Hash[Integer, Integer], production_hits: Hash[Integer, Integer]) -> void
32
+ def initialize(grammar_digest:, table_format_version:, state_count:, production_count:, sessions:, event_count:,
33
+ state_hits:, production_hits:)
34
+ @grammar_digest = validate_digest(grammar_digest)
35
+ @table_format_version = positive_integer(table_format_version, "table_format_version")
36
+ @state_count = total_integer(state_count, "state total", minimum: 1)
37
+ @production_count = total_integer(production_count, "production total", minimum: 0)
38
+ @sessions = positive_integer(sessions, "sessions")
39
+ @event_count = positive_integer(event_count, "events")
40
+ raise ArgumentError, "events must be at least sessions" if @event_count < @sessions
41
+
42
+ @state_hits = validate_hits(state_hits, @state_count, "state").freeze
43
+ @production_hits = validate_hits(production_hits, @production_count, "production").freeze
44
+ validate_event_bounds
45
+ freeze
46
+ end
47
+ # rubocop:enable Layout/LineLength
48
+
49
+ # @rbs () -> Hash[String, untyped]
50
+ def to_h
51
+ {
52
+ "ibex_coverage" => IDENTIFIER,
53
+ "schema_version" => SCHEMA_VERSION,
54
+ "grammar_digest" => @grammar_digest,
55
+ "table_format_version" => @table_format_version,
56
+ "totals" => { "states" => @state_count, "productions" => @production_count },
57
+ "sessions" => @sessions,
58
+ "events" => @event_count,
59
+ "state_hits" => hit_documents(@state_hits),
60
+ "production_hits" => hit_documents(@production_hits)
61
+ }
62
+ end
63
+
64
+ # @rbs (*untyped) -> String
65
+ def to_json(*)
66
+ "#{JSON.pretty_generate(to_h)}\n"
67
+ end
68
+
69
+ # @rbs (String path) -> Report
70
+ def self.load_file(path)
71
+ size = File.size(path)
72
+ if size > MAX_DOCUMENT_BYTES
73
+ raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
74
+ end
75
+
76
+ source = File.binread(path)
77
+ if source.bytesize > MAX_DOCUMENT_BYTES
78
+ raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
79
+ end
80
+
81
+ source.force_encoding(Encoding::UTF_8)
82
+ raise Ibex::Error, "#{path}:1:1: coverage document is not valid UTF-8" unless source.valid_encoding?
83
+
84
+ value = JSON.parse(source, max_nesting: 16, allow_nan: false)
85
+ from_h(value, source: path)
86
+ rescue JSON::ParserError => e
87
+ raise Ibex::Error, "#{path}:1:1: invalid coverage JSON: #{e.message}"
88
+ end
89
+
90
+ # @rbs (untyped value, source: String) -> Report
91
+ def self.from_h(value, source:)
92
+ document = string_hash(value, source, "coverage document")
93
+ invalid(source, "coverage object has unknown or missing fields") unless document.keys.sort == ROOT_KEYS
94
+ valid_schema = document["ibex_coverage"] == IDENTIFIER &&
95
+ document["schema_version"] == SCHEMA_VERSION
96
+ invalid(source, "unsupported coverage schema") unless valid_schema
97
+ totals = string_hash(document["totals"], source, "coverage totals")
98
+ invalid(source, "coverage totals have unknown or missing fields") unless totals.keys.sort == TOTAL_KEYS
99
+
100
+ state_count = totals["states"]
101
+ production_count = totals["productions"]
102
+ new(
103
+ grammar_digest: document["grammar_digest"],
104
+ table_format_version: document["table_format_version"],
105
+ state_count: state_count,
106
+ production_count: production_count,
107
+ sessions: document["sessions"],
108
+ event_count: document["events"],
109
+ state_hits: parse_hits(document["state_hits"], state_count, source, "state"),
110
+ production_hits: parse_hits(document["production_hits"], production_count, source, "production")
111
+ )
112
+ rescue ArgumentError => e
113
+ raise Ibex::Error, "#{source}:1:1: #{e.message}"
114
+ end
115
+
116
+ # @rbs (Array[Report] reports) -> Report
117
+ def self.merge(reports)
118
+ raise ArgumentError, "at least one coverage report is required" if reports.empty?
119
+
120
+ first = reports.first
121
+ reports.drop(1).each { |report| ensure_compatible(first, report) }
122
+ new(
123
+ grammar_digest: first.grammar_digest,
124
+ table_format_version: first.table_format_version,
125
+ state_count: first.state_count,
126
+ production_count: first.production_count,
127
+ sessions: checked_sum(reports.map(&:sessions), "sessions"),
128
+ event_count: checked_sum(reports.map(&:event_count), "events"),
129
+ state_hits: merge_hits(reports.map(&:state_hits), "state"),
130
+ production_hits: merge_hits(reports.map(&:production_hits), "production")
131
+ )
132
+ end
133
+
134
+ private
135
+
136
+ # @rbs (untyped input) -> String
137
+ def validate_digest(input)
138
+ valid = input.is_a?(String) && input.match?(/\Asha256:[0-9a-f]{64}\z/)
139
+ return input.dup.freeze if valid
140
+
141
+ raise ArgumentError, "grammar_digest must be a full lowercase SHA-256 digest"
142
+ end
143
+
144
+ # @rbs (untyped input, String name) -> Integer
145
+ def positive_integer(input, name)
146
+ return input if input.is_a?(Integer) && input.positive? && input <= MAX_COUNT
147
+
148
+ raise ArgumentError, "#{name} must be a bounded positive integer"
149
+ end
150
+
151
+ # @rbs (untyped input, String name, minimum: Integer) -> Integer
152
+ def total_integer(input, name, minimum:)
153
+ return input if input.is_a?(Integer) && input >= minimum && input <= MAX_TOTAL
154
+
155
+ raise ArgumentError, "#{name} must be between #{minimum} and #{MAX_TOTAL}"
156
+ end
157
+
158
+ # @rbs (Hash[Integer, Integer] input, Integer total, String kind) -> Hash[Integer, Integer]
159
+ def validate_hits(input, total, kind)
160
+ input.to_h do |id, count|
161
+ unless id.is_a?(Integer) && id >= 0 && id < total
162
+ raise ArgumentError, "#{kind} hit id #{id.inspect} is outside 0...#{total}"
163
+ end
164
+
165
+ [id, positive_integer(count, "#{kind} hit count")]
166
+ end.sort.to_h
167
+ end
168
+
169
+ # @rbs (Hash[Integer, Integer] hits) -> Array[Hash[String, Integer]]
170
+ def hit_documents(hits)
171
+ hits.map { |id, count| { "id" => id, "count" => count } }
172
+ end
173
+
174
+ # @rbs () -> void
175
+ def validate_event_bounds
176
+ raise ArgumentError, "state hit counts exceed events" if @state_hits.values.sum > @event_count
177
+ return unless @production_hits.values.sum > @event_count
178
+
179
+ raise ArgumentError, "production hit counts exceed events"
180
+ end
181
+
182
+ class << self
183
+ private
184
+
185
+ # @rbs (untyped value, String source, String name) -> Hash[String, untyped]
186
+ def string_hash(value, source, name)
187
+ return value if value.is_a?(Hash) && value.keys.all?(String)
188
+
189
+ invalid(source, "#{name} must be an object")
190
+ end
191
+
192
+ # @rbs (untyped value, untyped total, String source, String kind) -> Hash[Integer, Integer]
193
+ def parse_hits(value, total, source, kind)
194
+ invalid(source, "#{kind}_hits must be an array") unless value.is_a?(Array)
195
+ hits = {} #: Hash[Integer, Integer]
196
+ previous = -1
197
+ value.each do |entry|
198
+ id, count = parse_hit(entry, total, previous, source, kind)
199
+ hits[id] = count
200
+ previous = id
201
+ end
202
+ hits
203
+ end
204
+
205
+ # @rbs (untyped entry, untyped total, Integer previous, String source, String kind) -> [Integer, Integer]
206
+ def parse_hit(entry, total, previous, source, kind)
207
+ hit = string_hash(entry, source, "#{kind} hit")
208
+ invalid(source, "#{kind} hit has unknown or missing fields") unless hit.keys.sort == HIT_KEYS
209
+ id = hit["id"]
210
+ count = hit["count"]
211
+ unless id.is_a?(Integer) && id > previous
212
+ invalid(source, "#{kind} hits must have unique, ascending integer ids")
213
+ end
214
+ unless total.is_a?(Integer) && id >= 0 && id < total
215
+ invalid(source, "#{kind} hit id is outside the declared total")
216
+ end
217
+ unless count.is_a?(Integer) && count.positive? && count <= MAX_COUNT
218
+ invalid(source, "#{kind} hit count must be positive")
219
+ end
220
+ [id, count]
221
+ end
222
+
223
+ # @rbs (Report expected, Report actual) -> void
224
+ def ensure_compatible(expected, actual)
225
+ fields = %i[grammar_digest table_format_version state_count production_count]
226
+ mismatch = fields.find { |field| expected.public_send(field) != actual.public_send(field) }
227
+ return unless mismatch
228
+
229
+ raise ArgumentError, "coverage reports have different #{mismatch}"
230
+ end
231
+
232
+ # @rbs (Array[Integer] values, String name) -> Integer
233
+ def checked_sum(values, name)
234
+ total = values.sum
235
+ raise ArgumentError, "merged #{name} exceed the supported count" if total > MAX_COUNT
236
+
237
+ total
238
+ end
239
+
240
+ # @rbs (Array[Hash[Integer, Integer]] collections, String kind) -> Hash[Integer, Integer]
241
+ def merge_hits(collections, kind)
242
+ result = Hash.new(0) #: Hash[Integer, Integer]
243
+ collections.each do |hits|
244
+ hits.each do |id, count|
245
+ result[id] += count
246
+ raise ArgumentError, "merged #{kind} hit count exceeds the supported count" if result[id] > MAX_COUNT
247
+ end
248
+ end
249
+ result
250
+ end
251
+
252
+ # @rbs (String source, String message) -> bot
253
+ def invalid(source, message)
254
+ raise Ibex::Error, "#{source}:1:1: #{message}"
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end