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
@@ -5,6 +5,7 @@ require "json"
5
5
  module Ibex
6
6
  module IR
7
7
  # Stable JSON serialization for versioned pipeline IR.
8
+ # rubocop:disable Metrics/ModuleLength -- explicit versioned fields keep serialization changes auditable.
8
9
  module Serialize
9
10
  # @rbs!
10
11
  # private def validate_version: (untyped data) -> untyped
@@ -13,29 +14,39 @@ module Ibex
13
14
  # private def self.load_grammar: (untyped data) -> untyped
14
15
  # private def load_automaton: (untyped data) -> untyped
15
16
  # private def self.load_automaton: (untyped data) -> untyped
17
+ # private def load_lexer: (untyped data) -> untyped
18
+ # private def self.load_lexer: (untyped data) -> untyped
16
19
  # private def load_state: (untyped state, untyped grammar) -> untyped
17
20
  # private def self.load_state: (untyped state, untyped grammar) -> untyped
18
21
  # private def symbol_keyed: (untyped values, untyped grammar, ?actions: untyped) -> untyped
19
22
  # private def self.symbol_keyed: (untyped values, untyped grammar, ?actions: untyped) -> untyped
20
23
  # private def normalize_action: (untyped value) -> untyped
21
24
  # private def self.normalize_action: (untyped value) -> untyped
22
- # private def load_production: (untyped production) -> untyped
23
- # private def self.load_production: (untyped production) -> untyped
25
+ # private def load_production: (untyped production, Integer schema_version) -> untyped
26
+ # private def self.load_production: (untyped production, Integer schema_version) -> untyped
24
27
  # private def load_user_code_chunks: (untyped chunks) -> untyped
25
28
  # private def self.load_user_code_chunks: (untyped chunks) -> untyped
29
+ # private def load_symbol_metadata: (untyped symbol, String field) -> String?
30
+ # private def self.load_symbol_metadata: (untyped symbol, String field) -> String?
31
+ # private def load_grammar_tests: (untyped tests) -> untyped
32
+ # private def self.load_grammar_tests: (untyped tests) -> untyped
33
+ # private def symbol_source_position: (untyped symbol) -> String
34
+ # private def self.symbol_source_position: (untyped symbol) -> String
26
35
  # private def symbolize: (untyped value) -> untyped
27
36
  # private def self.symbolize: (untyped value) -> untyped
28
37
 
29
- # @rbs (Grammar | Automaton value) -> String
38
+ # @rbs (Grammar | Automaton | Lexer value) -> String
30
39
  def dump(value)
31
40
  "#{JSON.pretty_generate(value.to_h)}\n"
32
41
  end
33
42
  module_function :dump
34
43
 
35
- # @rbs (String source) -> (Grammar | Automaton)
44
+ # @rbs (String source) -> (Grammar | Automaton | Lexer)
36
45
  def load(source)
37
46
  data = JSON.parse(source)
38
47
  type = data.fetch("ibex_ir") { raise Ibex::Error, "(ir):1:1: missing ibex_ir discriminator" }
48
+ return load_lexer(data) if type == "lexer"
49
+
39
50
  validate_version(data)
40
51
  return load_grammar(data) if type == "grammar"
41
52
  return load_automaton(data) if type == "automaton"
@@ -52,33 +63,74 @@ module Ibex
52
63
  # @rbs skip
53
64
  def validate_version(data)
54
65
  version = data["schema_version"]
55
- return if version == SCHEMA_VERSION
66
+ return if SUPPORTED_SCHEMA_VERSIONS.include?(version)
56
67
 
57
- raise Ibex::Error, "(ir):1:1: unsupported schema_version #{version.inspect}; expected #{SCHEMA_VERSION}"
68
+ expected = SUPPORTED_SCHEMA_VERSIONS.join(", ")
69
+ raise Ibex::Error, "(ir):1:1: unsupported schema_version #{version.inspect}; expected one of #{expected}"
58
70
  end
59
71
 
60
72
  # @rbs skip
61
- def load_grammar(data)
73
+ def load_grammar(data) # rubocop:disable Metrics/AbcSize -- explicit fields preserve the public IR contract.
62
74
  empty_chunks = {} #: Hash[String, untyped]
75
+ empty_parameters = [] #: Array[untyped]
76
+ empty_printers = [] #: Array[untyped]
77
+ empty_tests = [] #: Array[untyped]
78
+ empty_recovery = { "sync_tokens" => [], "on_error_reduce" => [] } #: Hash[String, untyped]
79
+ schema_version = data.fetch("schema_version")
63
80
  symbols = data.fetch("symbols").map do |symbol|
64
81
  GrammarSymbol.new(id: symbol.fetch("id"), name: symbol.fetch("name"), kind: symbol.fetch("kind"),
65
82
  reserved: symbol.fetch("reserved"), precedence: symbolize(symbol["prec"]),
66
- location: symbolize(symbol["loc"]))
83
+ location: symbolize(symbol["loc"]),
84
+ display_name: load_symbol_metadata(symbol, "display_name"),
85
+ semantic_type: load_symbol_metadata(symbol, "semantic_type"),
86
+ documentation: symbol["doc"])
67
87
  end
68
- productions = data.fetch("productions").map { |production| load_production(production) }
88
+ productions = data.fetch("productions").map { |production| load_production(production, schema_version) }
69
89
  Grammar.new(class_name: data.fetch("class_name"), superclass: data["superclass"], start: data.fetch("start"),
70
90
  expect: data.fetch("expect"), options: symbolize(data.fetch("options")), symbols: symbols,
91
+ mode: (data["mode"] || "default").to_sym,
92
+ starts: data["starts"],
93
+ expect_rr: data["expect_rr"],
94
+ parser_parameters: symbolize(data.fetch("params", empty_parameters)),
95
+ value_printers: symbolize(data.fetch("printers", empty_printers)),
96
+ grammar_tests: load_grammar_tests(data.fetch("tests", empty_tests)),
97
+ lexer: data["lexer"] && load_lexer(data.fetch("lexer")),
98
+ recovery: symbolize(data.fetch("recovery", empty_recovery)),
71
99
  productions: productions, user_code: data.fetch("user_code"),
72
100
  conversions: data.fetch("conversions"), warnings: symbolize(data.fetch("warnings")),
73
- user_code_chunks: load_user_code_chunks(data.fetch("user_code_chunks", empty_chunks)))
74
- end
101
+ user_code_chunks: load_user_code_chunks(data.fetch("user_code_chunks", empty_chunks)),
102
+ schema_version: schema_version, source_provenance: symbolize(data["source_provenance"]),
103
+ migration: symbolize(data["migration"]))
104
+ end # rubocop:enable Metrics/AbcSize
75
105
 
76
106
  # @rbs skip
77
107
  def load_automaton(data)
78
108
  grammar = load_grammar(data.fetch("grammar"))
79
109
  states = data.fetch("states").map { |state| load_state(state, grammar) }
80
110
  Automaton.new(grammar: grammar, states: states, conflict_summary: symbolize(data.fetch("conflict_summary")),
81
- algorithm: data.fetch("algorithm"), grammar_digest: data.fetch("grammar_digest"))
111
+ algorithm: data.fetch("algorithm"), grammar_digest: data.fetch("grammar_digest"),
112
+ schema_version: data.fetch("schema_version"), entry_states: data["entry_states"])
113
+ end
114
+
115
+ # @rbs skip
116
+ def load_lexer(data)
117
+ version = data.fetch("schema_version")
118
+ unless SUPPORTED_LEXER_SCHEMA_VERSIONS.include?(version)
119
+ expected = SUPPORTED_LEXER_SCHEMA_VERSIONS.join(", ")
120
+ raise Ibex::Error,
121
+ "(ir):1:1: unsupported lexer schema_version #{version.inspect}; expected one of #{expected}"
122
+ end
123
+ rules = data.fetch("rules").map do |rule|
124
+ LexerRule.new(
125
+ id: rule.fetch("id"), state: rule.fetch("state"), kind: rule.fetch("kind").to_sym,
126
+ token: rule["token"], pattern: rule.fetch("pattern"), pattern_kind: rule.fetch("pattern_kind").to_sym,
127
+ options: rule.fetch("options"), action: rule["action"], location: symbolize(rule.fetch("loc"))
128
+ )
129
+ end
130
+ Lexer.new(
131
+ states: data.fetch("states"), rules: rules, warnings: symbolize(data.fetch("warnings")),
132
+ schema_version: version, source_provenance: symbolize(data["source_provenance"])
133
+ )
82
134
  end
83
135
 
84
136
  # @rbs skip
@@ -112,16 +164,19 @@ module Ibex
112
164
  end
113
165
 
114
166
  # @rbs skip
115
- def load_production(production)
167
+ def load_production(production, schema_version)
116
168
  action_data = production["action"]
117
169
  action = if action_data
118
170
  Action.new(code: action_data.fetch("code"), location: symbolize(action_data["loc"]),
119
171
  named_refs: symbolize(action_data.fetch("named_refs")),
120
- context_length: action_data.fetch("context_length"))
172
+ context_length: action_data.fetch("context_length"),
173
+ composition: symbolize(action_data["composition"]))
121
174
  end
122
175
  Production.new(id: production.fetch("id"), lhs: production.fetch("lhs"), rhs: production.fetch("rhs"),
123
176
  action: action, precedence_override: production["prec_override"],
124
- origin: symbolize(production.fetch("origin")))
177
+ origin: symbolize(production.fetch("origin")), documentation: production["doc"],
178
+ expansion: schema_version >= 2 ? symbolize(production["expansion"]) : nil,
179
+ node: schema_version >= 2 ? symbolize(production["node"]) : nil)
125
180
  end
126
181
 
127
182
  # @rbs skip
@@ -134,6 +189,39 @@ module Ibex
134
189
  end
135
190
  end
136
191
 
192
+ # @rbs skip
193
+ def load_symbol_metadata(symbol, field)
194
+ value = symbol[field]
195
+ return nil if value.nil?
196
+
197
+ position = symbol_source_position(symbol)
198
+ raise Ibex::Error, "#{position}: #{field} must be a String or null" unless value.is_a?(String)
199
+ raise Ibex::Error, "#{position}: #{field} must not be empty" if value.strip.empty?
200
+ raise Ibex::Error, "#{position}: #{field} must be a single line" if value.match?(/[\r\n]/)
201
+ raise Ibex::Error, "#{position}: #{field} must not contain control characters" if
202
+ value.match?(/[[:cntrl:]]/)
203
+
204
+ value
205
+ end
206
+
207
+ # @rbs skip
208
+ def symbol_source_position(symbol)
209
+ location = symbol["loc"]
210
+ return "(ir):1:1" unless location.is_a?(Hash)
211
+
212
+ file = location["file"]
213
+ line = location["line"]
214
+ column = location["column"]
215
+ return "(ir):1:1" unless file.is_a?(String) && line.is_a?(Integer) && column.is_a?(Integer)
216
+
217
+ "#{file}:#{line}:#{column}"
218
+ end
219
+
220
+ # @rbs skip
221
+ def load_grammar_tests(tests)
222
+ symbolize(tests).map { |test| test.merge(expectation: test.fetch(:expectation).to_sym) }
223
+ end
224
+
137
225
  # @rbs skip
138
226
  def symbolize(value)
139
227
  case value
@@ -142,13 +230,16 @@ module Ibex
142
230
  else value
143
231
  end
144
232
  end
145
- module_function :validate_version, :load_grammar, :load_automaton, :load_state, :symbol_keyed,
146
- :normalize_action, :load_production, :load_user_code_chunks, :symbolize
233
+ module_function :validate_version, :load_grammar, :load_automaton, :load_lexer, :load_state, :symbol_keyed,
234
+ :normalize_action, :load_production, :load_user_code_chunks, :load_symbol_metadata,
235
+ :symbol_source_position, :load_grammar_tests, :symbolize
147
236
 
148
237
  class << self
149
- private :validate_version, :load_grammar, :load_automaton, :load_state, :symbol_keyed,
150
- :normalize_action, :load_production, :load_user_code_chunks, :symbolize
238
+ private :validate_version, :load_grammar, :load_automaton, :load_lexer, :load_state, :symbol_keyed,
239
+ :normalize_action, :load_production, :load_user_code_chunks, :load_symbol_metadata,
240
+ :symbol_source_position, :load_grammar_tests, :symbolize
151
241
  end
152
242
  end
243
+ # rubocop:enable Metrics/ModuleLength
153
244
  end
154
245
  end
@@ -0,0 +1,345 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module IR
5
+ module Validator
6
+ # Structural and referential validation for a versioned Automaton IR JSON object.
7
+ # rubocop:disable Metrics/ClassLength -- inline type contracts accompany one cohesive document validator.
8
+ class AutomatonDocument < Base
9
+ ROOT_REQUIRED = %w[
10
+ ibex_ir schema_version algorithm grammar_digest grammar states conflict_summary
11
+ ].freeze #: Array[String]
12
+ V2_ROOT_OPTIONAL = %w[entry_states].freeze #: Array[String]
13
+ STATE_REQUIRED = %w[id items transitions actions gotos default_action conflicts].freeze #: Array[String]
14
+ ACTION_TYPES = %w[shift reduce accept error].freeze #: Array[String]
15
+ RESOLUTION_KINDS = %w[definition_order default_shift precedence associativity].freeze #: Array[String]
16
+
17
+ # @rbs @data: Hash[String, untyped]
18
+ # @rbs @states_by_id: Hash[Integer, Hash[String, untyped]]
19
+ # @rbs @grammar: GrammarDocument
20
+ # @rbs @version: Integer
21
+
22
+ # @rbs (Hash[String, untyped] data, ?version: Integer) -> void
23
+ def initialize(data, version: data.fetch("schema_version"))
24
+ super()
25
+ @data = data
26
+ @version = version
27
+ @states_by_id = {} #: Hash[Integer, Hash[String, untyped]]
28
+ end
29
+
30
+ # @rbs () -> self
31
+ def validate
32
+ record(@data, "$", ROOT_REQUIRED, @version >= 2 ? V2_ROOT_OPTIONAL : [])
33
+ literal(@data["ibex_ir"], "$.ibex_ir", "automaton")
34
+ literal(@data["schema_version"], "$.schema_version", @version)
35
+ enum(@data["algorithm"], "$.algorithm", %w[slr lalr1 ielr1 lr1])
36
+ validate_digest
37
+ grammar = object(@data["grammar"], "$.grammar")
38
+ literal(grammar["schema_version"], "$.grammar.schema_version", @version)
39
+ @grammar = GrammarDocument.new(grammar, path: "$.grammar", version: @version).validate
40
+ validate_state_records
41
+ validate_entry_states
42
+ validate_state_contents
43
+ validate_conflict_summary
44
+ self
45
+ end
46
+
47
+ private
48
+
49
+ # @rbs () -> void
50
+ def validate_entry_states
51
+ value = @data["entry_states"]
52
+ if value.nil?
53
+ invalid("$.entry_states", "is required for multiple start symbols") if @data.dig("grammar", "starts")
54
+ return
55
+ end
56
+
57
+ entries = object(value, "$.entry_states")
58
+ expected = @data.dig("grammar", "starts") || [@data.dig("grammar", "start")]
59
+ invalid("$.entry_states", "keys must equal grammar starts in order") unless entries.keys == expected
60
+ entries.each do |name, state|
61
+ nonnegative_integer(state, "$.entry_states.#{name}")
62
+ invalid("$.entry_states.#{name}", "references missing state #{state}") unless @states_by_id[state]
63
+ end
64
+ end
65
+
66
+ # @rbs () -> void
67
+ def validate_digest
68
+ digest = string(@data["grammar_digest"], "$.grammar_digest")
69
+ invalid("$.grammar_digest", "must be a sha256 digest") unless digest.match?(/\Asha256:[0-9a-f]{64}\z/)
70
+ end
71
+
72
+ # @rbs () -> void
73
+ def validate_state_records
74
+ states = array(@data["states"], "$.states")
75
+ invalid("$.states", "must contain at least one state") if states.empty?
76
+ states.each_with_index do |value, index|
77
+ path = "$.states[#{index}]"
78
+ state = record(value, path, STATE_REQUIRED)
79
+ id = nonnegative_integer(state["id"], "#{path}.id")
80
+ invalid("#{path}.id", "must equal its array index #{index}") unless id == index
81
+ @states_by_id[id] = state
82
+ end
83
+ end
84
+
85
+ # @rbs () -> void
86
+ def validate_state_contents
87
+ @states_by_id.each do |id, state|
88
+ path = "$.states[#{id}]"
89
+ validate_items(state["items"], "#{path}.items")
90
+ validate_transitions(state["transitions"], "#{path}.transitions")
91
+ validate_actions(state["actions"], "#{path}.actions")
92
+ validate_gotos(state["gotos"], "#{path}.gotos")
93
+ validate_parser_action(state["default_action"], "#{path}.default_action", nullable: true)
94
+ validate_conflicts(state["conflicts"], "#{path}.conflicts")
95
+ end
96
+ end
97
+
98
+ # @rbs (untyped value, String path) -> void
99
+ def validate_items(value, path)
100
+ array(value, path).each_with_index do |item, index|
101
+ item_path = "#{path}[#{index}]"
102
+ item = record(item, item_path, %w[production dot lookaheads])
103
+ production = integer(item["production"], "#{item_path}.production")
104
+ validate_item_production(production, item["dot"], item_path)
105
+ validate_lookaheads(item["lookaheads"], "#{item_path}.lookaheads")
106
+ end
107
+ end
108
+
109
+ # @rbs (Integer production_id, untyped dot_value, String path) -> void
110
+ def validate_item_production(production_id, dot_value, path)
111
+ dot = nonnegative_integer(dot_value, "#{path}.dot")
112
+ if production_id == -1
113
+ invalid("#{path}.dot", "must not exceed 1 for the augmented production") if dot > 1
114
+ return
115
+ end
116
+ production = @grammar.productions_by_id[production_id]
117
+ invalid("#{path}.production", "references missing production id #{production_id}") unless production
118
+ invalid("#{path}.dot", "exceeds production #{production_id} length") if dot > production["rhs"].length
119
+ end
120
+
121
+ # @rbs (untyped value, String path) -> void
122
+ def validate_lookaheads(value, path)
123
+ array(value, path).each_with_index do |name, index|
124
+ name = string(name, "#{path}[#{index}]")
125
+ symbol = @grammar.symbols_by_name[name]
126
+ invalid("#{path}[#{index}]", "references missing symbol #{name.inspect}") unless symbol
127
+ invalid("#{path}[#{index}]", "must reference a terminal") unless symbol["kind"] == "terminal"
128
+ end
129
+ end
130
+
131
+ # @rbs (untyped value, String path) -> void
132
+ def validate_transitions(value, path)
133
+ symbol_map(value, path) do |target, target_path, _symbol|
134
+ validate_state_reference(target, target_path)
135
+ end
136
+ end
137
+
138
+ # @rbs (untyped value, String path) -> void
139
+ def validate_actions(value, path)
140
+ symbol_map(value, path, kind: "terminal") do |action, action_path, _symbol|
141
+ validate_parser_action(action, action_path)
142
+ end
143
+ end
144
+
145
+ # @rbs (untyped value, String path) -> void
146
+ def validate_gotos(value, path)
147
+ symbol_map(value, path, kind: "nonterminal") do |target, target_path, _symbol|
148
+ validate_state_reference(target, target_path)
149
+ end
150
+ end
151
+
152
+ # @rbs (untyped value, String path, ?kind: String?) { (untyped, String, Hash[String, untyped]) -> void } -> void
153
+ def symbol_map(value, path, kind: nil, &block)
154
+ object(value, path).each do |name, item|
155
+ item_path = child_path(path, name)
156
+ symbol = @grammar.symbols_by_name[name]
157
+ invalid(item_path, "references missing symbol #{name.inspect}") unless symbol
158
+ invalid(item_path, "must reference a #{kind}") if kind && symbol["kind"] != kind
159
+ block.call(item, item_path, symbol)
160
+ end
161
+ end
162
+
163
+ # @rbs (untyped value, String path) -> void
164
+ def validate_state_reference(value, path)
165
+ id = nonnegative_integer(value, path)
166
+ invalid(path, "references missing state id #{id}") unless @states_by_id.key?(id)
167
+ end
168
+
169
+ # @rbs (untyped value, String path, ?nullable: bool) -> void
170
+ def validate_parser_action(value, path, nullable: false)
171
+ return if nullable && value.nil?
172
+
173
+ action = object(value, path)
174
+ type = enum(field(action, "type", path), "#{path}.type", ACTION_TYPES)
175
+ required = case type
176
+ when "shift" then %w[type state]
177
+ when "reduce" then %w[type production]
178
+ else %w[type]
179
+ end
180
+ record(action, path, required)
181
+ validate_state_reference(action["state"], "#{path}.state") if type == "shift"
182
+ validate_production_reference(action["production"], "#{path}.production") if type == "reduce"
183
+ end
184
+
185
+ # @rbs (untyped value, String path) -> void
186
+ def validate_production_reference(value, path)
187
+ id = nonnegative_integer(value, path)
188
+ invalid(path, "references missing production id #{id}") unless @grammar.productions_by_id.key?(id)
189
+ end
190
+
191
+ # @rbs (untyped value, String path) -> void
192
+ def validate_conflicts(value, path)
193
+ array(value, path).each_with_index do |conflict, index|
194
+ conflict_path = "#{path}[#{index}]"
195
+ conflict = object(conflict, conflict_path)
196
+ type = enum(field(conflict, "type", conflict_path), "#{conflict_path}.type",
197
+ %w[shift_reduce reduce_reduce])
198
+ if type == "shift_reduce"
199
+ validate_shift_reduce(conflict, conflict_path)
200
+ else
201
+ validate_reduce_reduce(conflict, conflict_path)
202
+ end
203
+ end
204
+ end
205
+
206
+ # @rbs (Hash[String, untyped] conflict, String path) -> void
207
+ def validate_shift_reduce(conflict, path)
208
+ record(conflict, path, %w[type symbol shift_to reduce resolution], %w[midrule_origins entries composite])
209
+ validate_conflict_symbol(conflict["symbol"], "#{path}.symbol")
210
+ validate_state_reference(conflict["shift_to"], "#{path}.shift_to")
211
+ validate_production_reference(conflict["reduce"], "#{path}.reduce")
212
+ validate_resolution(conflict["resolution"], "#{path}.resolution")
213
+ validate_midrule_origins(conflict["midrule_origins"], "#{path}.midrule_origins") if
214
+ conflict.key?("midrule_origins")
215
+ validate_conflict_entries(conflict, path)
216
+ end
217
+
218
+ # @rbs (Hash[String, untyped] conflict, String path) -> void
219
+ def validate_reduce_reduce(conflict, path)
220
+ record(conflict, path, %w[type symbol reductions resolution], %w[midrule_origins entries composite])
221
+ validate_conflict_symbol(conflict["symbol"], "#{path}.symbol")
222
+ reductions = array(conflict["reductions"], "#{path}.reductions")
223
+ invalid("#{path}.reductions", "must contain at least two productions") if reductions.length < 2
224
+ reductions.each_with_index do |id, index|
225
+ validate_production_reference(id, "#{path}.reductions[#{index}]")
226
+ end
227
+ if reductions.uniq.length != reductions.length
228
+ invalid("#{path}.reductions", "must contain unique production ids")
229
+ end
230
+ validate_resolution(conflict["resolution"], "#{path}.resolution", reductions: reductions)
231
+ validate_midrule_origins(conflict["midrule_origins"], "#{path}.midrule_origins") if
232
+ conflict.key?("midrule_origins")
233
+ validate_conflict_entries(conflict, path)
234
+ end
235
+
236
+ # @rbs (Hash[String, untyped] conflict, String path) -> void
237
+ def validate_conflict_entries(conflict, path)
238
+ if conflict.key?("entries")
239
+ starts = @data.dig("grammar", "starts") || [@data.dig("grammar", "start")]
240
+ entries = array(conflict["entries"], "#{path}.entries")
241
+ invalid("#{path}.entries", "must not be empty") if entries.empty?
242
+ entries.each_with_index do |name, index|
243
+ name = nonempty_string(name, "#{path}.entries[#{index}]")
244
+ invalid("#{path}.entries[#{index}]", "is not a grammar start symbol") unless starts.include?(name)
245
+ end
246
+ invalid("#{path}.entries", "must be unique") unless entries.uniq.length == entries.length
247
+ end
248
+ boolean(conflict["composite"], "#{path}.composite") if conflict.key?("composite")
249
+ end
250
+
251
+ # @rbs (untyped value, String path) -> void
252
+ def validate_midrule_origins(value, path)
253
+ origins = array(value, path)
254
+ invalid(path, "must not be empty") if origins.empty?
255
+ origins.each_with_index { |origin, index| location(origin, "#{path}[#{index}]") }
256
+ end
257
+
258
+ # @rbs (untyped value, String path) -> void
259
+ def validate_conflict_symbol(value, path)
260
+ name = string(value, path)
261
+ symbol = @grammar.symbols_by_name[name]
262
+ invalid(path, "references missing symbol #{name.inspect}") unless symbol
263
+ invalid(path, "must reference a terminal") unless symbol["kind"] == "terminal"
264
+ end
265
+
266
+ # @rbs (untyped value, String path, ?reductions: Array[Integer]?) -> void
267
+ def validate_resolution(value, path, reductions: nil)
268
+ resolution = record(value, path, %w[by chose], %w[associativity])
269
+ enum(resolution["by"], "#{path}.by", RESOLUTION_KINDS)
270
+ if reductions
271
+ chosen = resolution["chose"]
272
+ validate_production_reference(chosen, "#{path}.chose")
273
+ invalid("#{path}.chose", "must be one of the reductions") unless reductions.include?(chosen)
274
+ else
275
+ enum(resolution["chose"], "#{path}.chose", %w[shift reduce error])
276
+ end
277
+ return unless resolution.key?("associativity")
278
+
279
+ enum(resolution["associativity"], "#{path}.associativity", %w[left right nonassoc])
280
+ end
281
+
282
+ # @rbs () -> void
283
+ def validate_conflict_summary
284
+ path = "$.conflict_summary"
285
+ summary = record(
286
+ @data["conflict_summary"], path, %w[sr resolved_sr rr expected_sr expectation_met],
287
+ %w[expected_rr rr_expectation_met]
288
+ )
289
+ %w[sr resolved_sr rr expected_sr].each do |key|
290
+ nonnegative_integer(summary[key], "#{path}.#{key}")
291
+ end
292
+ boolean(summary["expectation_met"], "#{path}.expectation_met")
293
+ if summary.key?("expected_rr") || summary.key?("rr_expectation_met")
294
+ nonnegative_integer(summary["expected_rr"], "#{path}.expected_rr")
295
+ boolean(summary["rr_expectation_met"], "#{path}.rr_expectation_met")
296
+ end
297
+ validate_conflict_counts(summary, path)
298
+ validate_expectation(summary, path)
299
+ end
300
+
301
+ # @rbs (Hash[String, untyped] summary, String path) -> void
302
+ def validate_conflict_counts(summary, path)
303
+ conflicts = @states_by_id.values.flat_map { |state| state["conflicts"] }
304
+ shift_reduce = conflicts.select { |conflict| conflict["type"] == "shift_reduce" }
305
+ counts = {
306
+ "sr" => shift_reduce.count { |conflict| conflict.dig("resolution", "by") == "default_shift" },
307
+ "resolved_sr" => shift_reduce.count { |conflict| conflict.dig("resolution", "by") != "default_shift" },
308
+ "rr" => conflicts.count { |conflict| conflict["type"] == "reduce_reduce" }
309
+ }
310
+ counts.each do |key, actual|
311
+ next if summary[key] == actual
312
+
313
+ label = key == "rr" ? "reduce/reduce" : "shift/reduce"
314
+ invalid("#{path}.#{key}", "must equal the #{actual} recorded #{label} conflicts")
315
+ end
316
+ end
317
+
318
+ # @rbs (Hash[String, untyped] summary, String path) -> void
319
+ def validate_expectation(summary, path)
320
+ grammar_expect = @data.fetch("grammar").fetch("expect")
321
+ unless summary["expected_sr"] == grammar_expect
322
+ invalid("#{path}.expected_sr", "must equal embedded grammar expect #{grammar_expect}")
323
+ end
324
+ expected_met = summary["sr"] == summary["expected_sr"]
325
+ unless summary["expectation_met"] == expected_met
326
+ invalid("#{path}.expectation_met", "must be #{expected_met} for the recorded shift/reduce count")
327
+ end
328
+
329
+ grammar_expect_rr = @data.fetch("grammar")["expect_rr"]
330
+ return if grammar_expect_rr.nil?
331
+
332
+ unless summary["expected_rr"] == grammar_expect_rr
333
+ invalid("#{path}.expected_rr", "must equal embedded grammar expect_rr #{grammar_expect_rr}")
334
+ end
335
+ rr_expected_met = summary["rr"] == summary["expected_rr"]
336
+ return if summary["rr_expectation_met"] == rr_expected_met
337
+
338
+ invalid("#{path}.rr_expectation_met",
339
+ "must be #{rr_expected_met} for the recorded reduce/reduce count")
340
+ end
341
+ end
342
+ # rubocop:enable Metrics/ClassLength
343
+ end
344
+ end
345
+ end