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
@@ -4,37 +4,85 @@ require "set"
4
4
 
5
5
  module Ibex
6
6
  module LALR
7
- # Builds canonical LR(1) states and merges states with equal LR(0) cores.
7
+ # Builds deterministic SLR, direct LALR(1), or canonical LR(1) automata.
8
+ # rubocop:disable Metrics/ClassLength -- collection strategies share one action/conflict construction path.
8
9
  class Builder
9
10
  AUGMENTED_PRODUCTION = -1 #: Integer
10
11
 
11
- ALGORITHMS = %i[slr lalr lr1].freeze #: Array[Symbol]
12
+ ALGORITHMS = %i[slr lalr ielr lr1].freeze #: Array[Symbol]
13
+ LALR_STRATEGIES = %i[direct canonical_merge].freeze #: Array[Symbol]
12
14
 
13
15
  # @rbs @grammar: IR::Grammar
14
16
  # @rbs @algorithm: Symbol
17
+ # @rbs @lalr_strategy: Symbol
15
18
  # @rbs @sets: Analysis::Sets
16
19
  # @rbs @productions_by_lhs: Hash[Integer, Array[IR::Production]]
17
20
  # @rbs @resolver: ConflictResolver
18
-
19
- # @rbs (IR::Grammar grammar, ?algorithm: Symbol | String) -> void
20
- def initialize(grammar, algorithm: :lalr)
21
+ # @rbs @metrics: BuildMetrics?
22
+ # @rbs @start_names: Array[String]
23
+ # @rbs @entry_isolation: bool
24
+ # @rbs @attribute_entries: bool
25
+ # @rbs @canonical_suffix_lookahead_cache: Hash[Integer, Hash[Integer, Hash[Integer, Array[Integer]]]]
26
+ # @rbs @canonical_item_cache: Hash[Integer, Array[Array[lr_item?]?]]?
27
+ # @rbs @canonical_key_radices: [Integer, Integer, Integer]?
28
+
29
+ attr_reader :metrics #: BuildMetrics?
30
+
31
+ # @rbs (IR::Grammar grammar, ?algorithm: Symbol | String, ?lalr_strategy: Symbol | String,
32
+ # ?entry_isolation: bool, ?starts: Array[String]?, ?attribute_entries: bool) -> void
33
+ def initialize(grammar, algorithm: :lalr, lalr_strategy: :direct, entry_isolation: false,
34
+ starts: nil, attribute_entries: true)
21
35
  unless ALGORITHMS.include?(algorithm.to_sym)
22
36
  raise ArgumentError, "unknown parser algorithm #{algorithm.inspect}"
23
37
  end
38
+ unless LALR_STRATEGIES.include?(lalr_strategy.to_sym)
39
+ raise ArgumentError, "unknown LALR construction strategy #{lalr_strategy.inspect}"
40
+ end
24
41
 
25
42
  @grammar = grammar
26
43
  @algorithm = algorithm.to_sym
44
+ @lalr_strategy = lalr_strategy.to_sym
27
45
  @sets = Analysis::Sets.new(grammar)
28
46
  @productions_by_lhs = grammar.productions.group_by(&:lhs)
29
47
  @resolver = ConflictResolver.new(grammar)
48
+ @metrics = nil
49
+ @canonical_suffix_lookahead_cache = {}
50
+ @canonical_item_cache = nil
51
+ @canonical_key_radices = nil
52
+ @start_names = starts || grammar.starts
53
+ if @start_names.empty? || (@start_names - grammar.starts).any?
54
+ raise ArgumentError, "starts must be a nonempty subset of grammar starts"
55
+ end
56
+
57
+ @entry_isolation = entry_isolation
58
+ @attribute_entries = attribute_entries
30
59
  end
31
60
 
32
61
  # @rbs () -> IR::Automaton
33
62
  def build
34
- canonical_states, canonical_transitions = canonical_collection
35
- merged_items, merged_transitions = automaton_items(canonical_states, canonical_transitions)
63
+ return build_isolated_automaton if @entry_isolation && @start_names.length > 1
64
+
65
+ merged_items, merged_transitions, construction_states, canonical_states, strategy = automaton_collection
36
66
  states = build_states(merged_items, merged_transitions)
67
+ states = OnErrorReductions.apply(@grammar, states)
37
68
  states = DefaultReductions.apply(states, terminal_ids: @grammar.terminals.map(&:id))
69
+ entry_states = entry_states_for(merged_items)
70
+ states = attribute_entry_conflicts(states, entry_states) if @attribute_entries && @start_names.length > 1
71
+ summary = conflict_summary(states)
72
+ @metrics = BuildMetrics.new(
73
+ construction_states: construction_states,
74
+ canonical_states: canonical_states,
75
+ final_states: states.length,
76
+ strategy: strategy
77
+ )
78
+ IR::Automaton.new(grammar: @grammar, states: states, conflict_summary: summary,
79
+ algorithm: algorithm_name, entry_states: entry_states)
80
+ end
81
+
82
+ private
83
+
84
+ # @rbs (Array[IR::AutomatonState] states) -> IR::conflict_summary
85
+ def conflict_summary(states)
38
86
  conflicts = states.flat_map(&:conflicts)
39
87
  shift_reduce = conflicts.select { |item| item[:type] == :shift_reduce }
40
88
  counted_shift_reduce = shift_reduce.count { |item| item.dig(:resolution, :by) == :default_shift }
@@ -43,23 +91,69 @@ module Ibex
43
91
  rr: conflicts.count { |item| item[:type] == :reduce_reduce },
44
92
  expected_sr: @grammar.expect,
45
93
  expectation_met: counted_shift_reduce == @grammar.expect } #: IR::conflict_summary
46
- IR::Automaton.new(grammar: @grammar, states: states, conflict_summary: summary,
47
- algorithm: @algorithm == :lalr ? "lalr1" : @algorithm.to_s)
94
+ expected_rr = @grammar.expect_rr
95
+ if expected_rr
96
+ summary[:expected_rr] = expected_rr
97
+ summary[:rr_expectation_met] = summary[:rr] == expected_rr
98
+ end
99
+ summary
48
100
  end
49
101
 
50
- private
102
+ # @rbs () -> [Array[packed_items], transitions, Integer, Integer?, Symbol]
103
+ def automaton_collection
104
+ if @algorithm == :ielr
105
+ states, transitions = canonical_collection
106
+ items, merged_transitions = IELRPartition.new(@grammar, states, transitions).build
107
+ return [items, merged_transitions, states.length, states.length, :ielr_partition]
108
+ end
109
+
110
+ if @algorithm == :lr1
111
+ states, transitions = canonical_collection
112
+ return [pack_canonical_items(states), transitions, states.length, states.length, :canonical_lr1]
113
+ end
114
+
115
+ if @lalr_strategy == :canonical_merge
116
+ states, transitions = canonical_collection
117
+ items, merged_transitions = merge_lalr(states, transitions)
118
+ apply_slr_lookaheads(items) if @algorithm == :slr
119
+ return [items, merged_transitions, states.length, states.length, :canonical_merge]
120
+ end
121
+
122
+ if @grammar.starts.length > 1
123
+ states, transitions = canonical_collection
124
+ items, merged_transitions = merge_lalr(states, transitions)
125
+ apply_slr_lookaheads(items) if @algorithm == :slr
126
+ return [items, merged_transitions, states.length, states.length, :canonical_merge_multi_entry]
127
+ end
128
+
129
+ items, transitions = DirectLookaheads.new(@grammar, @sets).build
130
+ apply_slr_lookaheads(items) if @algorithm == :slr
131
+ collection = [items, transitions, items.length, nil, :direct_lalr]
132
+ collection #: [Array[packed_items], transitions, Integer, Integer?, Symbol]
133
+ ensure
134
+ @canonical_item_cache = nil
135
+ end
136
+
137
+ # @rbs () -> String
138
+ def algorithm_name
139
+ { lalr: "lalr1", ielr: "ielr1" }.fetch(@algorithm, @algorithm.to_s)
140
+ end
51
141
 
52
142
  # @rbs () -> [Array[item_set], transitions]
53
143
  def canonical_collection
54
- seed = Set[[AUGMENTED_PRODUCTION, 0, 0]] #: item_set
55
- states = [closure(seed)]
144
+ states = @start_names.map do |name|
145
+ seed = Set[canonical_item(augmented_production(name), 0, 0)] #: item_set
146
+ closure(seed)
147
+ end
56
148
  transitions = [] #: transitions
57
- indexes = { item_key(states.first) => 0 }
149
+ indexes = {} #: Hash[Array[Integer], Integer]
150
+ states.each_with_index { |items, index| indexes[item_key(items)] = index }
58
151
  cursor = 0
59
152
  while cursor < states.length
60
153
  transitions[cursor] = {}
61
- next_symbols(states[cursor]).each do |symbol_id|
62
- target = go_to(states[cursor], symbol_id)
154
+ kernels = shifted_kernels(states[cursor])
155
+ kernels.keys.sort.each do |symbol_id|
156
+ target = closure(kernels.fetch(symbol_id))
63
157
  key = item_key(target)
64
158
  target_id = indexes[key] ||= begin
65
159
  states << target
@@ -74,18 +168,27 @@ module Ibex
74
168
 
75
169
  # @rbs (item_set seed) -> item_set
76
170
  def closure(seed)
171
+ cache = (@canonical_item_cache ||= {}) #: Hash[Integer, Array[Array[lr_item?]?]]
77
172
  items = seed.dup
78
173
  queue = seed.to_a
79
- until queue.empty?
80
- production_id, dot, lookahead = queue.shift
174
+ cursor = 0
175
+ while cursor < queue.length
176
+ production_id, dot, lookahead = queue.fetch(cursor)
177
+ cursor += 1
81
178
  rhs = rhs_for(production_id)
82
179
  grammar_symbol = @grammar.symbol_by_id(rhs[dot])
83
180
  next unless grammar_symbol&.nonterminal?
84
181
 
85
- lookaheads = suffix_lookaheads(rhs.drop(dot + 1), lookahead)
182
+ lookaheads = canonical_suffix_lookaheads(production_id, dot, lookahead)
86
183
  @productions_by_lhs.fetch(grammar_symbol.id, Array.new(0)).each do |production|
184
+ production_cache = (cache[production.id] ||= []) #: Array[Array[lr_item?]?]
185
+ item_cache = (production_cache[0] ||= []) #: Array[lr_item?]
87
186
  lookaheads.each do |token_id|
88
- item = [production.id, 0, token_id] #: lr_item
187
+ item = item_cache[token_id]
188
+ unless item
189
+ item = [production.id, 0, token_id].freeze #: lr_item
190
+ item_cache[token_id] = item
191
+ end
89
192
  enqueue_item(items, queue, item)
90
193
  end
91
194
  end
@@ -100,32 +203,57 @@ module Ibex
100
203
  @grammar.terminals.filter_map { |terminal| terminal.id if bits.anybits?(1 << terminal.id) }
101
204
  end
102
205
 
103
- # @rbs (item_set items, Array[lr_item] queue, lr_item item) -> void
104
- def enqueue_item(items, queue, item)
105
- return if items.include?(item)
206
+ # @rbs (Integer production_id, Integer dot, Integer inherited) -> Array[Integer]
207
+ def canonical_suffix_lookaheads(production_id, dot, inherited)
208
+ production_cache = (@canonical_suffix_lookahead_cache[production_id] ||= {})
209
+ inherited_cache = (production_cache[dot] ||= {})
210
+ return inherited_cache.fetch(inherited) if inherited_cache.key?(inherited)
106
211
 
107
- items << item
108
- queue << item
212
+ suffix = rhs_for(production_id).drop(dot + 1)
213
+ inherited_cache[inherited] = suffix_lookaheads(suffix, inherited).freeze
109
214
  end
110
215
 
111
- # @rbs (item_set items) -> Array[Integer]
112
- def next_symbols(items)
113
- items.filter_map { |production_id, dot, _lookahead| rhs_for(production_id)[dot] }.uniq.sort
216
+ # @rbs (Integer production_id, Integer dot, Integer lookahead) -> lr_item
217
+ def canonical_item(production_id, dot, lookahead)
218
+ cache = (@canonical_item_cache ||= {}) #: Hash[Integer, Array[Array[lr_item?]?]]
219
+ production_cache = (cache[production_id] ||= []) #: Array[Array[lr_item?]?]
220
+ dot_cache = (production_cache[dot] ||= []) #: Array[lr_item?]
221
+ cached = dot_cache[lookahead]
222
+ return cached if cached
223
+
224
+ item = [production_id, dot, lookahead].freeze #: lr_item
225
+ dot_cache[lookahead] = item
114
226
  end
115
227
 
116
- # @rbs (item_set items, Integer symbol_id) -> item_set
117
- def go_to(items, symbol_id)
118
- moved = items.filter_map do |production_id, dot, lookahead|
119
- next unless rhs_for(production_id)[dot] == symbol_id
228
+ # @rbs (item_set items, Array[lr_item] queue, lr_item item) -> void
229
+ def enqueue_item(items, queue, item)
230
+ queue << item if items.add?(item)
231
+ end
120
232
 
121
- [production_id, dot + 1, lookahead] #: lr_item
233
+ # @rbs (item_set items) -> Hash[Integer, item_set]
234
+ def shifted_kernels(items)
235
+ cache = (@canonical_item_cache ||= {}) #: Hash[Integer, Array[Array[lr_item?]?]]
236
+ kernels = {} #: Hash[Integer, item_set]
237
+ items.each do |production_id, dot, lookahead|
238
+ symbol_id = rhs_for(production_id)[dot]
239
+ next unless symbol_id
240
+
241
+ production_cache = (cache[production_id] ||= []) #: Array[Array[lr_item?]?]
242
+ shifted_dot = dot + 1
243
+ item_cache = (production_cache[shifted_dot] ||= []) #: Array[lr_item?]
244
+ item = item_cache[lookahead]
245
+ unless item
246
+ item = [production_id, shifted_dot, lookahead].freeze #: lr_item
247
+ item_cache[lookahead] = item
248
+ end
249
+ (kernels[symbol_id] ||= Set.new) << item
122
250
  end
123
- closure(Set.new(moved))
251
+ kernels
124
252
  end
125
253
 
126
254
  # @rbs (Array[item_set] states, transitions transitions) -> [Array[packed_items], transitions]
127
255
  def merge_lalr(states, transitions)
128
- groups = {} #: Hash[Array[item_core], Integer]
256
+ groups = {} #: Hash[Array[Integer], Integer]
129
257
  state_groups = states.map do |items|
130
258
  core = core_key(items)
131
259
  groups[core] ||= groups.length
@@ -145,15 +273,6 @@ module Ibex
145
273
  [merged, merged_transitions]
146
274
  end
147
275
 
148
- # @rbs (Array[item_set] canonical_states, transitions canonical_transitions) -> [Array[packed_items], transitions]
149
- def automaton_items(canonical_states, canonical_transitions)
150
- return [pack_canonical_items(canonical_states), canonical_transitions] if @algorithm == :lr1
151
-
152
- items, transitions = merge_lalr(canonical_states, canonical_transitions)
153
- apply_slr_lookaheads(items) if @algorithm == :slr
154
- [items, transitions]
155
- end
156
-
157
276
  # @rbs (Array[item_set] states) -> Array[packed_items]
158
277
  def pack_canonical_items(states)
159
278
  states.map do |items|
@@ -176,7 +295,7 @@ module Ibex
176
295
 
177
296
  # @rbs (Integer production_id) -> Array[Integer]
178
297
  def slr_lookaheads(production_id)
179
- return [0] if production_id == AUGMENTED_PRODUCTION
298
+ return [0] if production_id.negative?
180
299
 
181
300
  lhs = @grammar.productions.fetch(production_id).lhs
182
301
  bits = @sets.follow_bits.fetch(lhs)
@@ -187,7 +306,8 @@ module Ibex
187
306
  def build_states(merged_items, transitions)
188
307
  merged_items.each_with_index.map do |item_map, state_id|
189
308
  items = item_map.sort.map do |(production, dot), lookaheads|
190
- IR::AutomatonItem.new(production: production, dot: dot, lookaheads: lookaheads.to_a)
309
+ visible_production = production.negative? ? AUGMENTED_PRODUCTION : production
310
+ IR::AutomatonItem.new(production: visible_production, dot: dot, lookaheads: lookaheads.to_a)
191
311
  end
192
312
  build_state(state_id, items, transitions[state_id])
193
313
  end
@@ -209,17 +329,36 @@ module Ibex
209
329
  end
210
330
  add_completed_actions(items, candidates)
211
331
  actions, conflicts = resolve_actions(candidates)
332
+ conflicts = conflicts.map { |conflict| attribute_midrule_conflict(conflict) }
212
333
  IR::AutomatonState.new(id: state_id, items: items, transitions: transitions, actions: actions,
213
334
  gotos: gotos, conflicts: conflicts)
214
335
  end
215
336
 
337
+ # Conflict hashes are produced locally immediately before this boundary.
338
+ # Keeping the parameter untyped avoids duplicating both discriminated
339
+ # hash variants solely to add their common optional provenance field.
340
+ # @rbs (untyped conflict) -> IR::conflict
341
+ def attribute_midrule_conflict(conflict)
342
+ production_ids = if conflict[:type] == :shift_reduce
343
+ [conflict[:reduce]]
344
+ else
345
+ conflict[:reductions]
346
+ end
347
+ origins = production_ids.filter_map do |production_id|
348
+ production = @grammar.productions.fetch(production_id)
349
+ production.origin[:loc] if production.origin[:kind] == :inline_action
350
+ end
351
+ conflict[:midrule_origins] = origins.uniq unless origins.empty?
352
+ conflict
353
+ end
354
+
216
355
  # @rbs (Array[IR::AutomatonItem] items, Hash[Integer, Array[IR::parser_action]] candidates) -> void
217
356
  def add_completed_actions(items, candidates)
218
357
  items.each do |item|
219
358
  next unless item.dot == rhs_for(item.production).length
220
359
 
221
360
  item.lookaheads.each do |lookahead|
222
- action = if item.production == AUGMENTED_PRODUCTION
361
+ action = if item.production.negative?
223
362
  { type: :accept } #: IR::accept_action
224
363
  else
225
364
  { type: :reduce, production: item.production } #: IR::reduce_action
@@ -246,25 +385,213 @@ module Ibex
246
385
 
247
386
  # @rbs (Integer production_id) -> Array[Integer]
248
387
  def rhs_for(production_id)
249
- if production_id == AUGMENTED_PRODUCTION
250
- start = @grammar.symbol(@grammar.start) || raise(Ibex::Error, "missing start symbol")
388
+ if production_id.negative?
389
+ name = start_name_for_augmented(production_id)
390
+ start = @grammar.symbol(name) || raise(Ibex::Error, "missing start symbol #{name}")
251
391
  return [start.id]
252
392
  end
253
393
 
254
394
  @grammar.productions.fetch(production_id).rhs
255
395
  end
256
396
 
257
- # @rbs (item_set items) -> Array[item_core]
397
+ # @rbs () -> [Integer, Integer, Integer]
398
+ def canonical_key_radices
399
+ cached = @canonical_key_radices
400
+ return cached if cached
401
+
402
+ longest_rhs = @grammar.productions.map { |production| production.rhs.length }.max || 0
403
+ highest_terminal_id = @grammar.terminals.map(&:id).max || 0
404
+ radices = [
405
+ @grammar.starts.length,
406
+ [longest_rhs, 1].max + 1,
407
+ highest_terminal_id + 1
408
+ ] #: [Integer, Integer, Integer]
409
+ @canonical_key_radices = radices.freeze
410
+ end
411
+
412
+ # @rbs (item_set items) -> Array[Integer]
258
413
  def core_key(items)
414
+ production_offset, dot_radix, = canonical_key_radices
259
415
  items.map do |production, dot, _lookahead|
260
- [production, dot] #: item_core
416
+ ((production + production_offset) * dot_radix) + dot
261
417
  end.uniq.sort
262
418
  end
263
419
 
264
- # @rbs (item_set items) -> Array[lr_item]
420
+ # @rbs (item_set items) -> Array[Integer]
265
421
  def item_key(items)
266
- items.to_a.sort
422
+ production_offset, dot_radix, lookahead_radix = canonical_key_radices
423
+ items.map do |production, dot, lookahead|
424
+ core = ((production + production_offset) * dot_radix) + dot
425
+ (core * lookahead_radix) + lookahead
426
+ end.sort
427
+ end
428
+
429
+ # @rbs (String name) -> Integer
430
+ def augmented_production(name)
431
+ index = @grammar.starts.index(name)
432
+ raise Ibex::Error, "missing start symbol #{name}" unless index
433
+
434
+ AUGMENTED_PRODUCTION - index
435
+ end
436
+
437
+ # @rbs (Integer production_id) -> String
438
+ def start_name_for_augmented(production_id)
439
+ @grammar.starts.fetch(-production_id - 1)
440
+ end
441
+
442
+ # @rbs (Array[packed_items] items) -> Hash[String, Integer]
443
+ def entry_states_for(items)
444
+ @start_names.to_h do |name|
445
+ production = augmented_production(name)
446
+ state = items.index { |item_map| item_map.key?([production, 0]) }
447
+ raise Ibex::Error, "missing initial state for start symbol #{name}" unless state
448
+
449
+ [name, state]
450
+ end
451
+ end
452
+
453
+ # @rbs () -> IR::Automaton
454
+ def build_isolated_automaton
455
+ entries = @start_names.map { |name| isolated_entry(name) }
456
+ states = [] #: Array[IR::AutomatonState]
457
+ entry_states = {} #: Hash[String, Integer]
458
+ construction_states = 0
459
+ canonical_counts = [] #: Array[Integer?]
460
+ entries.each do |name, automaton, metrics|
461
+ offset = states.length
462
+ entry_states[name] = offset + automaton.entry_states.fetch(name)
463
+ states.concat(automaton.states.map { |state| offset_state(state, offset, name) })
464
+ construction_states += metrics.construction_states
465
+ canonical_counts << metrics.canonical_states
466
+ end
467
+ canonical_states = canonical_counts.compact.sum if canonical_counts.none?(&:nil?)
468
+ @metrics = BuildMetrics.new(
469
+ construction_states: construction_states,
470
+ canonical_states: canonical_states,
471
+ final_states: states.length,
472
+ strategy: :entry_isolation
473
+ )
474
+ IR::Automaton.new(
475
+ grammar: @grammar, states: states, conflict_summary: conflict_summary(states),
476
+ algorithm: algorithm_name, entry_states: entry_states
477
+ )
478
+ end
479
+
480
+ # @rbs (String name) -> [String, IR::Automaton, BuildMetrics]
481
+ def isolated_entry(name)
482
+ builder = self.class.new(
483
+ @grammar, algorithm: @algorithm, lalr_strategy: @lalr_strategy,
484
+ starts: [name], attribute_entries: false
485
+ )
486
+ automaton = builder.build
487
+ metrics = builder.metrics
488
+ raise Ibex::Error, "missing build metrics for start symbol #{name}" unless metrics
489
+
490
+ [name, automaton, metrics]
491
+ end
492
+
493
+ # @rbs (IR::AutomatonState state, Integer offset, String entry) -> IR::AutomatonState
494
+ def offset_state(state, offset, entry)
495
+ actions = state.actions.transform_values { |action| offset_action(action, offset) }
496
+ conflicts = state.conflicts.map { |conflict| offset_conflict(conflict, offset, entry) }
497
+ IR::AutomatonState.new(
498
+ id: state.id + offset, items: state.items,
499
+ transitions: state.transitions.transform_values { |target| target + offset },
500
+ actions: actions, gotos: state.gotos.transform_values { |target| target + offset },
501
+ default_action: offset_default_action(state.default_action, offset), conflicts: conflicts
502
+ )
503
+ end
504
+
505
+ # @rbs (IR::parser_action action, Integer offset) -> IR::parser_action
506
+ def offset_action(action, offset)
507
+ return action unless action[:type] == :shift
508
+
509
+ shift = action #: IR::shift_action
510
+ { type: :shift, state: shift[:state] + offset }
511
+ end
512
+
513
+ # @rbs (IR::parser_action? action, Integer offset) -> IR::parser_action?
514
+ def offset_default_action(action, offset)
515
+ action && offset_action(action, offset)
516
+ end
517
+
518
+ # @rbs (IR::conflict conflict, Integer offset, String entry) -> IR::conflict
519
+ def offset_conflict(conflict, offset, entry)
520
+ if conflict[:type] == :shift_reduce
521
+ shift_reduce = conflict #: IR::shift_reduce_conflict
522
+ shifted = shift_reduce.dup #: IR::shift_reduce_conflict
523
+ shifted[:shift_to] = shift_reduce[:shift_to] + offset
524
+ shifted[:entries] = [entry]
525
+ return shifted
526
+ end
527
+
528
+ reduce_reduce = conflict #: IR::reduce_reduce_conflict
529
+ shifted = reduce_reduce.dup #: IR::reduce_reduce_conflict
530
+ shifted[:entries] = [entry]
531
+ shifted
532
+ end
533
+
534
+ # @rbs (Array[IR::AutomatonState] states, Hash[String, Integer] entry_states) ->
535
+ # Array[IR::AutomatonState]
536
+ def attribute_entry_conflicts(states, entry_states)
537
+ reachability = entry_reachability(states, entry_states)
538
+ isolated = isolated_conflict_fingerprints
539
+ states.map do |state|
540
+ conflicts = state.conflicts.map do |conflict| # @type var conflict: IR::conflict
541
+ entries = @start_names.select { |name| reachability.fetch(state.id).include?(name) }
542
+ attributed = conflict.dup #: IR::conflict
543
+ attributed[:entries] = entries
544
+ attributed[:composite] = true unless isolated.include?(conflict_fingerprint(conflict))
545
+ attributed
546
+ end #: Array[IR::conflict]
547
+ IR::AutomatonState.new(
548
+ id: state.id, items: state.items, transitions: state.transitions, actions: state.actions,
549
+ gotos: state.gotos, default_action: state.default_action, conflicts: conflicts
550
+ )
551
+ end
552
+ end
553
+
554
+ # @rbs (Array[IR::AutomatonState] states, Hash[String, Integer] entry_states) -> Array[Array[String]]
555
+ def entry_reachability(states, entry_states)
556
+ reachable = Array.new(states.length) { [] } #: Array[Array[String]]
557
+ entry_states.each do |name, initial|
558
+ queue = [initial]
559
+ visited = {} #: Hash[Integer, bool]
560
+ until queue.empty?
561
+ state_id = queue.shift
562
+ next if visited[state_id]
563
+
564
+ visited[state_id] = true
565
+ reachable.fetch(state_id) << name
566
+ states.fetch(state_id).transitions.each_value { |target| queue << target }
567
+ end
568
+ end
569
+ reachable
570
+ end
571
+
572
+ # @rbs () -> Set[Array[untyped]]
573
+ def isolated_conflict_fingerprints
574
+ @start_names.each_with_object(Set.new) do |name, fingerprints|
575
+ isolated = self.class.new(
576
+ @grammar, algorithm: @algorithm, lalr_strategy: @lalr_strategy,
577
+ starts: [name], attribute_entries: false
578
+ ).build
579
+ isolated.states.each do |state|
580
+ state.conflicts.each { |conflict| fingerprints << conflict_fingerprint(conflict) }
581
+ end
582
+ end
583
+ end
584
+
585
+ # @rbs (IR::conflict conflict) -> Array[untyped]
586
+ def conflict_fingerprint(conflict)
587
+ reductions = if conflict[:type] == :shift_reduce
588
+ [conflict[:reduce]]
589
+ else
590
+ conflict[:reductions]
591
+ end
592
+ [conflict[:type], conflict[:symbol], reductions]
267
593
  end
268
594
  end
595
+ # rubocop:enable Metrics/ClassLength
269
596
  end
270
597
  end
@@ -90,6 +90,7 @@ module Ibex
90
90
  when :left then [reduction, { by: :associativity, associativity: :left, chose: :reduce }]
91
91
  when :right then [shift, { by: :associativity, associativity: :right, chose: :shift }]
92
92
  when :nonassoc then [{ type: :error }, { by: :associativity, associativity: :nonassoc, chose: :error }]
93
+ when :precedence then [shift, { by: :default_shift, chose: :shift }]
93
94
  else raise Ibex::Error, "unknown associativity #{associativity.inspect}"
94
95
  end
95
96
  end
@@ -25,6 +25,9 @@ module Ibex
25
25
  # @rbs @max_configurations: Integer
26
26
  # @rbs @explored: Integer
27
27
  # @rbs @input_tokens: Array[Integer]
28
+ # @rbs @initial_state: Integer
29
+
30
+ attr_reader :explored #: Integer
28
31
 
29
32
  # @rbs (IR::Automaton automaton, IR::AutomatonState state, IR::conflict conflict,
30
33
  # ?max_tokens: Integer, ?max_configurations: Integer) -> void
@@ -40,14 +43,17 @@ module Ibex
40
43
  @max_configurations = max_configurations
41
44
  @explored = 0
42
45
  @input_tokens = @grammar.terminals.reject(&:reserved).sort_by(&:name).map(&:id)
46
+ entry = conflict[:entries]&.first || @grammar.start
47
+ @initial_state = automaton.entry_states.fetch(entry)
43
48
  end
44
49
 
45
50
  # @rbs () -> search_result?
46
51
  def call
47
52
  return unless @lookahead
48
53
 
49
- queue = [[configuration([0], Array.new(0)), Array.new(0)]] #: Array[[Configuration, Array[Integer]]]
50
- visited = { [0] => true }
54
+ initial = configuration([@initial_state], Array.new(0))
55
+ queue = [[initial, Array.new(0)]] #: Array[[Configuration, Array[Integer]]]
56
+ visited = { [@initial_state] => true }
51
57
  until queue.empty? || exhausted?
52
58
  current, prefix = queue.shift
53
59
  conflict_configurations(current).each do |candidate|
@@ -59,6 +65,9 @@ module Ibex
59
65
  nil
60
66
  end
61
67
 
68
+ # @rbs () -> bool
69
+ def exhausted? = @explored >= @max_configurations
70
+
62
71
  private
63
72
 
64
73
  # @rbs (Configuration current) -> Array[Configuration]
@@ -323,9 +332,6 @@ module Ibex
323
332
 
324
333
  # @rbs () -> Integer
325
334
  def count_configuration = (@explored += 1)
326
-
327
- # @rbs () -> bool
328
- def exhausted? = @explored >= @max_configurations
329
335
  end
330
336
  # rubocop:enable Metrics/ClassLength
331
337
  end
@@ -24,6 +24,19 @@ module Ibex
24
24
  end
25
25
  end
26
26
 
27
+ # @rbs (Integer state_id, Integer conflict_index) -> IR::counterexample
28
+ def for_conflict(state_id, conflict_index)
29
+ state = @automaton.states.find { |candidate| candidate.id == state_id }
30
+ raise ArgumentError, "unknown automaton state #{state_id}" unless state
31
+
32
+ unless conflict_index.is_a?(Integer) && conflict_index >= 0 && conflict_index < state.conflicts.length
33
+ raise ArgumentError, "conflict index #{conflict_index.inspect} is invalid for automaton state #{state_id}"
34
+ end
35
+
36
+ conflict = state.conflicts.fetch(conflict_index)
37
+ build_example(state, conflict)
38
+ end
39
+
27
40
  private
28
41
 
29
42
  # @rbs (IR::AutomatonState state, IR::conflict conflict) -> IR::counterexample
@@ -45,7 +58,7 @@ module Ibex
45
58
 
46
59
  # @rbs (IR::AutomatonState state, IR::conflict conflict) -> IR::counterexample
47
60
  def reachability_example(state, conflict)
48
- path = shortest_state_path(state.id)
61
+ path = shortest_state_path(state.id, conflict)
49
62
  lookahead = @grammar.symbol(conflict[:symbol])
50
63
  terminal_ids = path.flat_map { |symbol_id| @shortest_yields[symbol_id] || [] }
51
64
  terminal_ids << lookahead.id if lookahead
@@ -53,10 +66,12 @@ module Ibex
53
66
  lookahead_index: terminal_ids.length - 1, unifying: false, interpretations: interpretations(conflict) }
54
67
  end
55
68
 
56
- # @rbs (Integer target) -> Array[Integer]
57
- def shortest_state_path(target)
58
- queue = [[0, Array.new(0)]] #: Array[[Integer, Array[Integer]]]
59
- visited = { 0 => true }
69
+ # @rbs (Integer target, IR::conflict conflict) -> Array[Integer]
70
+ def shortest_state_path(target, conflict)
71
+ entry = conflict[:entries]&.first || @grammar.start
72
+ initial = @automaton.entry_states.fetch(entry)
73
+ queue = [[initial, Array.new(0)]] #: Array[[Integer, Array[Integer]]]
74
+ visited = { initial => true }
60
75
  until queue.empty?
61
76
  state_id, path = queue.shift
62
77
  return path if state_id == target