ibex 0.2.0 → 0.3.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 (437) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +95 -272
  3. data/docs/architecture.md +187 -35
  4. data/docs/bison-import.md +103 -0
  5. data/docs/comparison-policy.md +127 -0
  6. data/docs/configuration-model.md +59 -0
  7. data/docs/configuration-report.md +28 -0
  8. data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
  9. data/docs/conflict-explanation-study.md +62 -0
  10. data/docs/construction-profiling.md +157 -0
  11. data/docs/cst-migration.md +3 -4
  12. data/docs/cst.md +24 -0
  13. data/docs/decisions/0000-template.md +24 -0
  14. data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
  15. data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
  16. data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
  17. data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
  18. data/docs/decisions/0005-contained-grammar-composition.md +34 -0
  19. data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
  20. data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
  21. data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
  22. data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
  23. data/docs/decisions/0010-committed-runtime-observation.md +34 -0
  24. data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
  25. data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
  26. data/docs/decisions/0013-transactional-generation-publication.md +37 -0
  27. data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
  28. data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
  29. data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
  30. data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
  31. data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
  32. data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
  33. data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
  34. data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
  35. data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
  36. data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
  37. data/docs/decisions/0024-direct-ielr-construction.md +42 -0
  38. data/docs/decisions/README.md +74 -0
  39. data/docs/declarative-configuration.md +262 -0
  40. data/docs/development.md +179 -2
  41. data/docs/direct-ielr-decision.md +120 -0
  42. data/docs/direct-multi-entry-decision.md +82 -0
  43. data/docs/editor-setup.md +2 -1
  44. data/docs/error-ux-review-rubric-v1.md +117 -0
  45. data/docs/error-ux-reviews/v1/records/README.md +22 -0
  46. data/docs/error-ux-round2-review-status-v1.json +27 -0
  47. data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
  48. data/docs/error-ux-round2-v1.json +1581 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +54 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +184 -13
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/ja/bison-import.md +51 -0
  56. data/docs/ja/stable-api.md +53 -0
  57. data/docs/lexer-construction-profile.md +109 -0
  58. data/docs/lexer-migration.md +5 -0
  59. data/docs/maturity.md +128 -0
  60. data/docs/project-site-strategy.md +54 -0
  61. data/docs/racc-migration-evidence.md +45 -0
  62. data/docs/racc-migration.md +11 -0
  63. data/docs/release-readiness.md +126 -68
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +144 -34
  67. data/docs/status.md +43 -0
  68. data/docs/syntax-sessions.md +183 -0
  69. data/docs/table-artifact.md +125 -0
  70. data/docs/test-interactions.md +219 -0
  71. data/docs/verification-report.md +137 -0
  72. data/docs/verifier-trust-boundary.md +199 -0
  73. data/docs/workloads.md +132 -0
  74. data/lib/ibex/analysis/digraph.rb +128 -0
  75. data/lib/ibex/analysis.rb +1 -0
  76. data/lib/ibex/bison_import/importer.rb +516 -0
  77. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  78. data/lib/ibex/bison_import.rb +200 -0
  79. data/lib/ibex/bounded_subprocess.rb +171 -0
  80. data/lib/ibex/cli/ambiguity.rb +14 -5
  81. data/lib/ibex/cli/analysis.rb +134 -0
  82. data/lib/ibex/cli/bison_import.rb +121 -0
  83. data/lib/ibex/cli/config.rb +128 -0
  84. data/lib/ibex/cli/diagnostics.rb +31 -11
  85. data/lib/ibex/cli/documentation.rb +11 -8
  86. data/lib/ibex/cli/equiv.rb +169 -0
  87. data/lib/ibex/cli/error_messages.rb +11 -4
  88. data/lib/ibex/cli/explain.rb +14 -5
  89. data/lib/ibex/cli/fix.rb +203 -0
  90. data/lib/ibex/cli/formatting.rb +11 -10
  91. data/lib/ibex/cli/fuzz.rb +200 -0
  92. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  93. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  94. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  95. data/lib/ibex/cli/grammar_tests.rb +32 -14
  96. data/lib/ibex/cli/ir_tools.rb +5 -61
  97. data/lib/ibex/cli/outputs.rb +60 -33
  98. data/lib/ibex/cli/reduce.rb +202 -0
  99. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  100. data/lib/ibex/cli/samples.rb +49 -23
  101. data/lib/ibex/cli/verify.rb +116 -0
  102. data/lib/ibex/cli/watch.rb +6 -5
  103. data/lib/ibex/cli.rb +357 -56
  104. data/lib/ibex/codegen/action_locations.rb +1 -1
  105. data/lib/ibex/codegen/action_method_source.rb +5 -6
  106. data/lib/ibex/codegen/action_source.rb +3 -2
  107. data/lib/ibex/codegen/ambiguity.rb +5 -4
  108. data/lib/ibex/codegen/explain.rb +123 -55
  109. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  110. data/lib/ibex/codegen/rbs.rb +15 -7
  111. data/lib/ibex/codegen/report.rb +25 -13
  112. data/lib/ibex/codegen/ruby.rb +28 -64
  113. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  114. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  115. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  116. data/lib/ibex/configuration/explanation.rb +421 -0
  117. data/lib/ibex/configuration/inspector.rb +232 -0
  118. data/lib/ibex/configuration.rb +580 -0
  119. data/lib/ibex/coverage/collector.rb +21 -11
  120. data/lib/ibex/coverage/event_stream.rb +13 -7
  121. data/lib/ibex/coverage/report.rb +26 -18
  122. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  123. data/lib/ibex/delta_reducer.rb +99 -0
  124. data/lib/ibex/diff.rb +140 -0
  125. data/lib/ibex/equiv/machine.rb +135 -0
  126. data/lib/ibex/equiv.rb +373 -0
  127. data/lib/ibex/fix.rb +557 -0
  128. data/lib/ibex/frontend/ast.rb +22 -2
  129. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  130. data/lib/ibex/frontend/diagnostic.rb +1 -1
  131. data/lib/ibex/frontend/formatter.rb +72 -26
  132. data/lib/ibex/frontend/generated_parser.rb +147 -117
  133. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  134. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  135. data/lib/ibex/frontend/generation.rb +1 -1
  136. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  137. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  138. data/lib/ibex/frontend/regenerator.rb +1 -0
  139. data/lib/ibex/frontend/resolution.rb +2 -1
  140. data/lib/ibex/frontend/resolver.rb +1 -1
  141. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  142. data/lib/ibex/frontend/source_cursor.rb +2 -2
  143. data/lib/ibex/frontend/source_span.rb +9 -1
  144. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  145. data/lib/ibex/frontend/token_adapter.rb +10 -0
  146. data/lib/ibex/frontend.rb +1 -0
  147. data/lib/ibex/fuzz.rb +219 -0
  148. data/lib/ibex/generation_input.rb +1 -1
  149. data/lib/ibex/generation_manifest.rb +36 -25
  150. data/lib/ibex/generation_transaction.rb +1 -1
  151. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  152. data/lib/ibex/generation_transaction_validation.rb +2 -2
  153. data/lib/ibex/grammar_tests.rb +1 -1
  154. data/lib/ibex/ir/automaton_ir.rb +61 -19
  155. data/lib/ibex/ir/grammar_ir.rb +76 -45
  156. data/lib/ibex/ir/lexer_ir.rb +5 -5
  157. data/lib/ibex/ir/parser_contract.rb +135 -0
  158. data/lib/ibex/ir/serialize.rb +203 -76
  159. data/lib/ibex/ir/validator/automaton.rb +64 -34
  160. data/lib/ibex/ir/validator/base.rb +51 -33
  161. data/lib/ibex/ir/validator/grammar.rb +113 -83
  162. data/lib/ibex/ir/validator/lexer.rb +2 -2
  163. data/lib/ibex/ir/validator.rb +2 -1
  164. data/lib/ibex/ir.rb +16 -3
  165. data/lib/ibex/lalr/build_metrics.rb +50 -2
  166. data/lib/ibex/lalr/builder.rb +191 -56
  167. data/lib/ibex/lalr/conflict_search.rb +15 -4
  168. data/lib/ibex/lalr/counterexample.rb +19 -14
  169. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  170. data/lib/ibex/lalr/goto_follows.rb +229 -0
  171. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  172. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  173. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  174. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  175. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  176. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  177. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  178. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  179. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  180. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  181. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  182. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  183. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  184. data/lib/ibex/lalr.rb +52 -1
  185. data/lib/ibex/location.rb +3 -3
  186. data/lib/ibex/lsp/document_handlers.rb +9 -7
  187. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  188. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  189. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  190. data/lib/ibex/lsp/position_codec.rb +6 -2
  191. data/lib/ibex/lsp/request_handlers.rb +3 -2
  192. data/lib/ibex/lsp/request_support.rb +14 -7
  193. data/lib/ibex/lsp/server.rb +13 -11
  194. data/lib/ibex/lsp/symbol_index.rb +13 -13
  195. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  196. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  197. data/lib/ibex/lsp/transport.rb +13 -3
  198. data/lib/ibex/lsp/workspace.rb +5 -4
  199. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  200. data/lib/ibex/lsp.rb +1 -0
  201. data/lib/ibex/messages/en.yml +28 -0
  202. data/lib/ibex/messages/ja.yml +28 -0
  203. data/lib/ibex/messages.rb +68 -0
  204. data/lib/ibex/metrics.rb +175 -0
  205. data/lib/ibex/normalize/declarations.rb +2 -0
  206. data/lib/ibex/normalize/diagnostics.rb +63 -38
  207. data/lib/ibex/normalize/expander.rb +5 -4
  208. data/lib/ibex/normalize/expression.rb +8 -6
  209. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  210. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  211. data/lib/ibex/normalize/lexer.rb +3 -3
  212. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  213. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  214. data/lib/ibex/normalize/parameters.rb +31 -4
  215. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  216. data/lib/ibex/normalize.rb +10 -20
  217. data/lib/ibex/racc_migration/report.rb +6 -3
  218. data/lib/ibex/rake_task.rb +1 -1
  219. data/lib/ibex/samples.rb +43 -13
  220. data/lib/ibex/table_artifact/builder.rb +266 -0
  221. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  222. data/lib/ibex/table_artifact/document.rb +39 -0
  223. data/lib/ibex/table_artifact/executor.rb +187 -0
  224. data/lib/ibex/table_artifact/serializer.rb +57 -0
  225. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  226. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  227. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  228. data/lib/ibex/table_artifact/validator.rb +253 -0
  229. data/lib/ibex/table_artifact.rb +88 -0
  230. data/lib/ibex/table_simulation/result.rb +6 -2
  231. data/lib/ibex/table_simulation/step.rb +3 -1
  232. data/lib/ibex/tables.rb +14 -0
  233. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  234. data/lib/ibex/verification_report/builder.rb +159 -0
  235. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  236. data/lib/ibex/verification_report/logical_path.rb +68 -0
  237. data/lib/ibex/verification_report/validator.rb +329 -0
  238. data/lib/ibex/verification_report.rb +69 -0
  239. data/lib/ibex/verify/action_correspondence.rb +138 -0
  240. data/lib/ibex/verify/language_witness.rb +295 -0
  241. data/lib/ibex/verify/reference_collection.rb +190 -0
  242. data/lib/ibex/verify/result.rb +73 -0
  243. data/lib/ibex/verify/verifier.rb +598 -0
  244. data/lib/ibex/verify.rb +15 -0
  245. data/lib/ibex/version.rb +1 -1
  246. data/lib/ibex/watch/runner.rb +10 -1
  247. data/lib/ibex/watch/source_snapshot.rb +13 -8
  248. data/lib/ibex.rb +10 -0
  249. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  250. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  251. data/schema/bison-import-v1.schema.json +106 -0
  252. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  253. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  254. data/schema/construction-profile-v1.schema.json +779 -0
  255. data/schema/diff-v1.schema.json +41 -0
  256. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  257. data/schema/equiv-v1.schema.json +64 -0
  258. data/schema/error-ux-review-v1.schema.json +609 -0
  259. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  260. data/schema/error-ux-round2-v1.schema.json +519 -0
  261. data/schema/explain-v1.schema.json +63 -1
  262. data/schema/fix-v1.schema.json +64 -0
  263. data/schema/fix-v2.schema.json +66 -0
  264. data/schema/fix-v3.schema.json +101 -0
  265. data/schema/fuzz-regression-v1.schema.json +79 -0
  266. data/schema/fuzz-v1.schema.json +114 -0
  267. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  268. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  269. data/schema/grammar-ir.schema.json +271 -0
  270. data/schema/ielr-benchmark-v1.schema.json +85 -0
  271. data/schema/lexer-profile-v1.schema.json +491 -0
  272. data/schema/metrics-v1.schema.json +60 -0
  273. data/schema/reduce-v1.schema.json +22 -0
  274. data/schema/reduce-v2.schema.json +63 -0
  275. data/schema/table-artifact-v1.schema.json +377 -0
  276. data/schema/verification-report-v1.schema.json +222 -0
  277. data/schema/verify-v1.schema.json +42 -0
  278. data/sig/ibex/analysis/digraph.rbs +19 -0
  279. data/sig/ibex/bison_import/importer.rbs +140 -0
  280. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  281. data/sig/ibex/bison_import.rbs +95 -0
  282. data/sig/ibex/bounded_subprocess.rbs +76 -0
  283. data/sig/ibex/cli/ambiguity.rbs +6 -0
  284. data/sig/ibex/cli/analysis.rbs +43 -0
  285. data/sig/ibex/cli/bison_import.rbs +32 -0
  286. data/sig/ibex/cli/config.rbs +35 -0
  287. data/sig/ibex/cli/diagnostics.rbs +16 -3
  288. data/sig/ibex/cli/documentation.rbs +5 -1
  289. data/sig/ibex/cli/equiv.rbs +49 -0
  290. data/sig/ibex/cli/error_messages.rbs +6 -0
  291. data/sig/ibex/cli/explain.rbs +6 -0
  292. data/sig/ibex/cli/fix.rbs +53 -0
  293. data/sig/ibex/cli/formatting.rbs +5 -1
  294. data/sig/ibex/cli/fuzz.rbs +47 -0
  295. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  296. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  297. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  298. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  299. data/sig/ibex/cli/ir_tools.rbs +7 -19
  300. data/sig/ibex/cli/outputs.rbs +9 -3
  301. data/sig/ibex/cli/reduce.rbs +60 -0
  302. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  303. data/sig/ibex/cli/samples.rbs +8 -0
  304. data/sig/ibex/cli/verify.rbs +28 -0
  305. data/sig/ibex/cli/watch.rbs +2 -2
  306. data/sig/ibex/cli.rbs +79 -5
  307. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  308. data/sig/ibex/codegen/explain.rbs +45 -25
  309. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  310. data/sig/ibex/codegen/rbs.rbs +12 -10
  311. data/sig/ibex/codegen/report.rbs +12 -12
  312. data/sig/ibex/codegen/ruby.rbs +18 -15
  313. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  314. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  315. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  316. data/sig/ibex/configuration/explanation.rbs +195 -0
  317. data/sig/ibex/configuration/inspector.rbs +53 -0
  318. data/sig/ibex/configuration.rbs +242 -0
  319. data/sig/ibex/coverage/collector.rbs +16 -14
  320. data/sig/ibex/coverage/event_stream.rbs +12 -10
  321. data/sig/ibex/coverage/report.rbs +20 -18
  322. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  323. data/sig/ibex/delta_reducer.rbs +38 -0
  324. data/sig/ibex/diff.rbs +47 -0
  325. data/sig/ibex/equiv/machine.rbs +54 -0
  326. data/sig/ibex/equiv.rbs +129 -0
  327. data/sig/ibex/fix.rbs +138 -0
  328. data/sig/ibex/frontend/ast.rbs +33 -7
  329. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  330. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  331. data/sig/ibex/frontend/formatter.rbs +38 -30
  332. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  333. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  334. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  335. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  336. data/sig/ibex/frontend/resolution.rbs +3 -2
  337. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  338. data/sig/ibex/frontend/source_span.rbs +5 -2
  339. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  340. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  341. data/sig/ibex/fuzz.rbs +71 -0
  342. data/sig/ibex/generation_input.rbs +2 -2
  343. data/sig/ibex/generation_manifest.rbs +35 -32
  344. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  345. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  346. data/sig/ibex/grammar_tests.rbs +2 -2
  347. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  348. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  349. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  350. data/sig/ibex/ir/parser_contract.rbs +69 -0
  351. data/sig/ibex/ir/serialize.rbs +63 -28
  352. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  353. data/sig/ibex/ir/validator/base.rbs +35 -31
  354. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  355. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  356. data/sig/ibex/ir.rbs +9 -3
  357. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  358. data/sig/ibex/lalr/builder.rbs +75 -24
  359. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  360. data/sig/ibex/lalr/counterexample.rbs +17 -14
  361. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  362. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  363. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  364. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  365. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  366. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  367. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  368. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  369. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  370. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  371. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  372. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  373. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  374. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  375. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  376. data/sig/ibex/lalr.rbs +11 -1
  377. data/sig/ibex/location.rbs +6 -6
  378. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  379. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  380. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  381. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  382. data/sig/ibex/lsp/position_codec.rbs +8 -4
  383. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  384. data/sig/ibex/lsp/request_support.rbs +8 -8
  385. data/sig/ibex/lsp/server.rbs +16 -16
  386. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  387. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  388. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  389. data/sig/ibex/lsp/transport.rbs +18 -6
  390. data/sig/ibex/lsp/workspace.rbs +3 -2
  391. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  392. data/sig/ibex/messages.rbs +26 -0
  393. data/sig/ibex/metrics.rbs +50 -0
  394. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  395. data/sig/ibex/normalize/expander.rbs +2 -2
  396. data/sig/ibex/normalize/expression.rbs +10 -8
  397. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  398. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  399. data/sig/ibex/normalize/lexer.rbs +4 -4
  400. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  401. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  402. data/sig/ibex/normalize/parameters.rbs +13 -8
  403. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  404. data/sig/ibex/normalize.rbs +11 -5
  405. data/sig/ibex/racc_migration/report.rbs +8 -6
  406. data/sig/ibex/rake_task.rbs +1 -1
  407. data/sig/ibex/samples.rbs +13 -4
  408. data/sig/ibex/table_artifact/builder.rbs +93 -0
  409. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  410. data/sig/ibex/table_artifact/document.rbs +27 -0
  411. data/sig/ibex/table_artifact/executor.rbs +66 -0
  412. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  413. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  414. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  415. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  416. data/sig/ibex/table_artifact/validator.rbs +66 -0
  417. data/sig/ibex/table_artifact.rbs +32 -0
  418. data/sig/ibex/table_simulation/result.rbs +8 -4
  419. data/sig/ibex/table_simulation/step.rbs +4 -2
  420. data/sig/ibex/tables.rbs +10 -9
  421. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  422. data/sig/ibex/verification_report/builder.rbs +53 -0
  423. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  424. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  425. data/sig/ibex/verification_report/validator.rbs +114 -0
  426. data/sig/ibex/verification_report.rbs +44 -0
  427. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  428. data/sig/ibex/verify/language_witness.rbs +112 -0
  429. data/sig/ibex/verify/reference_collection.rbs +63 -0
  430. data/sig/ibex/verify/result.rbs +52 -0
  431. data/sig/ibex/verify/verifier.rbs +170 -0
  432. data/sig/ibex/verify.rbs +6 -0
  433. data/sig/ibex/watch/runner.rbs +12 -2
  434. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  435. metadata +232 -12
  436. data/lib/ibex/ir/migration.rb +0 -120
  437. data/sig/ibex/ir/migration.rbs +0 -34
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ require "set"
7
+ require_relative "bits"
8
+ require_relative "split_state"
9
+ require_relative "annotator"
10
+ require_relative "split_stability"
11
+
12
+ module Ibex
13
+ module LALR
14
+ module IELR
15
+ # Splits LR(0) isocores only where an inadequacy would change the
16
+ # resolved action. New states copy their transition arrays; no mutable
17
+ # transition table is shared between isocores.
18
+ class StateSplitter
19
+ attr_reader :annotations #: Array[Array[Annotation]]
20
+ attr_reader :states #: Array[SplitState]
21
+ attr_reader :lalr_isocores #: Array[Integer]
22
+ attr_reader :split_states #: Integer
23
+ attr_reader :inadequacies #: Array[Array[Inadequacy]]
24
+ attr_reader :split_stable_discarded #: Integer
25
+
26
+ # @rbs (IR::Grammar grammar, Array[core_set] states, transitions transitions,
27
+ # Array[packed_items] items, GotoFollows goto_follows, ?resolver: ConflictResolver?) -> void
28
+ # rubocop:disable Metrics/AbcSize -- initialization wires the phase tables once.
29
+ def initialize(grammar, states, transitions, items, goto_follows, resolver: nil)
30
+ @grammar = grammar
31
+ @base_states = states
32
+ @base_transitions = transitions
33
+ @base_items = items
34
+ @goto_follows = goto_follows
35
+ @resolver = resolver || ConflictResolver.new(grammar)
36
+ @kernel_cores = states.map { |state| kernel_items(state) }
37
+ annotator = Annotator.new(grammar, states, transitions, items, goto_follows, resolver: @resolver)
38
+ @annotations = annotator.build
39
+ @inadequacies = annotator.inadequacies
40
+ @split_stable_discarded = annotator.split_stable_discarded
41
+ @item_lookaheads = annotator.item_lookaheads
42
+ @states = states.each_with_index.map do |_state, state_id|
43
+ SplitState.new(core: @kernel_cores.fetch(state_id),
44
+ transitions: transitions.fetch(state_id).sort_by(&:first).map(&:dup),
45
+ lalr_isocore: state_id)
46
+ end
47
+ @lalr_isocores = states.each_index.to_a
48
+ @isocore_nexts = states.each_index.to_a
49
+ @lookaheads_recomputed = Array.new(states.length, false)
50
+ @lookahead_sets = states.each_with_index.map do |_state, state_id|
51
+ @kernel_cores.fetch(state_id).each_index.map do |index|
52
+ @item_lookaheads.fetch(state_id, index)
53
+ end
54
+ end
55
+ @filters = {}
56
+ @split_states = 0
57
+ end
58
+ # rubocop:enable Metrics/AbcSize
59
+
60
+ # @rbs () -> [Array[packed_items], transitions]
61
+ def build
62
+ state_id = 0
63
+ while state_id < @states.length
64
+ @states.fetch(state_id).transitions.each_index do |index|
65
+ target = @states.fetch(state_id).transitions.fetch(index).fetch(1)
66
+ compute_state(state_id, target, index)
67
+ end
68
+ state_id += 1
69
+ end
70
+ [pack_items, packed_transitions]
71
+ end
72
+
73
+ private
74
+
75
+ # @rbs (Integer from_id, Integer to_id, Integer transition_index) -> void
76
+ def compute_state(from_id, to_id, transition_index)
77
+ incoming = propagate_lookaheads(from_id, to_id)
78
+ target = to_id
79
+ found = false
80
+ loop do
81
+ if compatible?(target, incoming)
82
+ found = true
83
+ break
84
+ end
85
+ break if @isocore_nexts.fetch(target) == to_id
86
+
87
+ target = @isocore_nexts.fetch(target)
88
+ end
89
+
90
+ if !found
91
+ target = append_isocore(target, incoming)
92
+ elsif !@lookaheads_recomputed.fetch(target)
93
+ raise Ibex::Error, "IELR target changed before initial lookahead computation" unless target == to_id
94
+
95
+ @lookahead_sets[target] = incoming
96
+ @lookaheads_recomputed[target] = true
97
+ else
98
+ merge_lookaheads(target, incoming)
99
+ end
100
+ @states.fetch(from_id).transitions.fetch(transition_index)[1] = target
101
+ end
102
+
103
+ # @rbs (Integer from_id, Integer to_id) -> Array[Integer]
104
+ def propagate_lookaheads(from_id, to_id)
105
+ filters = lookahead_set_filters(to_id)
106
+ @kernel_cores.fetch(to_id).each_with_index.map do |(production_id, dot), index|
107
+ bits = if dot > 1
108
+ previous = kernel_index_of(from_id, production_id, dot - 1)
109
+ previous ? @lookahead_sets.fetch(from_id).fetch(previous) : 0
110
+ elsif dot == 1
111
+ compute_goto_follow_set(from_id, lhs_for(production_id))
112
+ else
113
+ raise Ibex::Error, "IELR transition produced a dot-zero kernel"
114
+ end
115
+ bits & filters.fetch(index)
116
+ end
117
+ end
118
+
119
+ # @rbs (Integer state_id, Integer nonterminal_id) -> Integer
120
+ def compute_goto_follow_set(state_id, nonterminal_id)
121
+ goto_id = @goto_follows.goto_for(@lalr_isocores.fetch(state_id), nonterminal_id)
122
+ raise Ibex::Error, "missing GOTO for #{nonterminal_id} from #{state_id}" unless goto_id
123
+
124
+ bits = @goto_follows.always_follows.fetch(goto_id)
125
+ Bits.each_set_bit(@goto_follows.follow_kernel_items.fetch(goto_id)) do |index|
126
+ bits |= @lookahead_sets.fetch(state_id).fetch(index)
127
+ end
128
+ bits
129
+ end
130
+
131
+ # @rbs (Integer state_id) -> Array[Integer]
132
+ def lookahead_set_filters(state_id)
133
+ core_id = @lalr_isocores.fetch(state_id)
134
+ return @filters.fetch(core_id) if @filters.key?(core_id)
135
+
136
+ filters = Array.new(@kernel_cores.fetch(core_id).length, 0)
137
+ @annotations.fetch(core_id).each do |annotation|
138
+ token_bit = 1 << annotation.inadequacy.token
139
+ annotation.matrix.each do |row|
140
+ next if row.nil?
141
+
142
+ Bits.each_set_bit(row) { |index| filters[index] |= token_bit }
143
+ end
144
+ end
145
+ @filters[core_id] = filters
146
+ end
147
+
148
+ # @rbs (Integer state_id, Array[Integer]) -> bool
149
+ def compatible?(state_id, incoming)
150
+ return true unless @lookaheads_recomputed.fetch(state_id)
151
+
152
+ @annotations.fetch(@lalr_isocores.fetch(state_id)).all? do |annotation|
153
+ current = dominant_contribution(annotation, @lookahead_sets.fetch(state_id))
154
+ next true unless current
155
+
156
+ candidate = dominant_contribution(annotation, incoming)
157
+ !candidate || current == candidate
158
+ end
159
+ end
160
+
161
+ # @rbs (Annotation, Array[Integer]) -> Object?
162
+ def dominant_contribution(annotation, lookaheads)
163
+ token_bit = 1 << annotation.inadequacy.token
164
+ selected = annotation.inadequacy.contributions.each_index.select do |index|
165
+ row = annotation.matrix.fetch(index)
166
+ row.nil? || Bits.each_set_bit(row).any? { |kernel| lookaheads.fetch(kernel).anybits?(token_bit) }
167
+ end
168
+ return nil if selected.empty?
169
+
170
+ contributions = selected.map { |index| annotation.inadequacy.contributions.fetch(index) }
171
+ SplitStability.resolve(@resolver, annotation.inadequacy.token, contributions)
172
+ end
173
+
174
+ # @rbs (Integer after_id, Array[Integer]) -> Integer
175
+ def append_isocore(after_id, lookaheads)
176
+ source = @states.fetch(after_id)
177
+ @states << SplitState.new(
178
+ core: source.core.dup,
179
+ transitions: source.transitions.map(&:dup),
180
+ lalr_isocore: @lalr_isocores.fetch(after_id)
181
+ )
182
+ appended = @states.length - 1
183
+ @lalr_isocores << @lalr_isocores.fetch(after_id)
184
+ @isocore_nexts << @isocore_nexts.fetch(after_id)
185
+ @isocore_nexts[after_id] = appended
186
+ @lookaheads_recomputed << true
187
+ @lookahead_sets << lookaheads
188
+ @split_states += 1
189
+ appended
190
+ end
191
+
192
+ # @rbs (Integer state_id, Array[Integer]) -> void
193
+ def merge_lookaheads(state_id, incoming)
194
+ changed = false
195
+ incoming.each_with_index do |bits, index|
196
+ added = bits & ~@lookahead_sets.fetch(state_id).fetch(index)
197
+ next if added.zero?
198
+
199
+ @lookahead_sets.fetch(state_id)[index] |= added
200
+ changed = true
201
+ end
202
+ return unless changed
203
+
204
+ @states.fetch(state_id).transitions.each_index do |index|
205
+ target = @states.fetch(state_id).transitions.fetch(index).fetch(1)
206
+ break unless @lookaheads_recomputed.fetch(target)
207
+
208
+ compute_state(state_id, target, index)
209
+ end
210
+ end
211
+
212
+ # @rbs () -> Array[packed_items]
213
+ def pack_items
214
+ @states.each_with_index.map do |state, state_id|
215
+ packed = Hash.new { |hash, key| hash[key] = Set.new }
216
+ @base_items.fetch(state.lalr_isocore).each do |core, values|
217
+ packed[core].merge(values)
218
+ end
219
+ state.core.each_with_index do |core, index|
220
+ packed[core] = Set.new(Bits.each_set_bit(@lookahead_sets.fetch(state_id).fetch(index)).to_a)
221
+ end
222
+ packed
223
+ end
224
+ end
225
+
226
+ # @rbs () -> transitions
227
+ def packed_transitions
228
+ @states.map { |state| state.transitions.to_h { |symbol, target| [symbol, target] } }
229
+ end
230
+
231
+ # @rbs (core_set items) -> Array[item_core]
232
+ def kernel_items(items)
233
+ items.select { |production_id, dot| production_id.negative? || dot.positive? }.to_a.sort
234
+ end
235
+
236
+ # @rbs (Integer state_id, Integer production_id, Integer dot) -> Integer?
237
+ def kernel_index_of(state_id, production_id, dot)
238
+ @kernel_cores.fetch(@lalr_isocores.fetch(state_id)).index([production_id, dot])
239
+ end
240
+
241
+ # @rbs (Integer production_id) -> Integer
242
+ def lhs_for(production_id)
243
+ return @grammar.symbol(@grammar.starts.fetch(-production_id - 1)).id if production_id.negative?
244
+
245
+ @grammar.productions.fetch(production_id).lhs
246
+ end
247
+
248
+ # @rbs (Integer production_id) -> Array[Integer]
249
+ def rhs_for(production_id)
250
+ return [lhs_for(production_id)] if production_id.negative?
251
+
252
+ @grammar.productions.fetch(production_id).rhs
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ # steep:ignore:end
@@ -8,24 +8,38 @@ module Ibex
8
8
  # Deterministically merges compatible canonical LR(1) states and splits
9
9
  # partitions until their outgoing transitions are congruent.
10
10
  class IELRPartition
11
+ # @rbs! type contribution_action = [:shift] | [:reduce, Integer] | [:accept]
12
+
11
13
  AUGMENTED_PRODUCTION = -1 #: Integer
12
14
 
13
15
  # @rbs @grammar: IR::Grammar
14
16
  # @rbs @states: Array[item_set]
15
17
  # @rbs @transitions: transitions
16
- # @rbs @contributions: Array[Hash[Integer, Set[Array[untyped]]]]
18
+ # @rbs @contributions: Array[Hash[Integer, Set[contribution_action]]]
19
+ # @rbs @initial_partition_count: Integer?
20
+ # @rbs @final_partition_count: Integer?
21
+ # @rbs @profile: bool
22
+
23
+ attr_reader :initial_partition_count #: Integer?
24
+ attr_reader :final_partition_count #: Integer?
17
25
 
18
- # @rbs (IR::Grammar grammar, Array[item_set] states, transitions transitions) -> void
19
- def initialize(grammar, states, transitions)
26
+ # @rbs (IR::Grammar grammar, Array[item_set] states, transitions transitions, ?profile: bool) -> void
27
+ def initialize(grammar, states, transitions, profile: false)
20
28
  @grammar = grammar
21
29
  @states = states
22
30
  @transitions = transitions
23
31
  @contributions = Array.new(states.length) { |state_id| action_contributions(state_id) }
32
+ @initial_partition_count = nil
33
+ @final_partition_count = nil
34
+ @profile = profile
24
35
  end
25
36
 
26
37
  # @rbs () -> [Array[packed_items], transitions]
27
38
  def build
28
- partitions = refine_transitions(initial_partitions)
39
+ partitions = initial_partitions
40
+ @initial_partition_count = partitions.length if @profile
41
+ partitions = refine_transitions(partitions)
42
+ @final_partition_count = partitions.length if @profile
29
43
  indexes = partition_indexes(partitions)
30
44
  items = partitions.map { |members| merge_items(members) }
31
45
  transitions = partitions.map do |members|
@@ -62,7 +76,7 @@ module Ibex
62
76
  # action. Every nonempty member cell must equal the merged cell.
63
77
  # @rbs (state_partition members) -> bool
64
78
  def compatible?(members)
65
- merged = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[Array[untyped]]]
79
+ merged = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[contribution_action]]
66
80
  members.each do |state_id|
67
81
  @contributions.fetch(state_id).each { |token_id, actions| merged[token_id].merge(actions) }
68
82
  end
@@ -114,9 +128,9 @@ module Ibex
114
128
  merged
115
129
  end
116
130
 
117
- # @rbs (Integer state_id) -> Hash[Integer, Set[Array[untyped]]]
131
+ # @rbs (Integer state_id) -> Hash[Integer, Set[contribution_action]]
118
132
  def action_contributions(state_id)
119
- actions = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[Array[untyped]]]
133
+ actions = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, Set[contribution_action]]
120
134
  @transitions.fetch(state_id).each_key do |symbol_id|
121
135
  symbol = @grammar.symbol_by_id(symbol_id)
122
136
  actions[symbol_id] << [:shift] if symbol&.terminal?
@@ -124,7 +138,7 @@ module Ibex
124
138
  @states.fetch(state_id).each do |production_id, dot, lookahead|
125
139
  next unless dot == rhs_for(production_id).length
126
140
 
127
- action = production_id.negative? ? [:accept] : [:reduce, production_id]
141
+ action = production_id.negative? ? [:accept] : [:reduce, production_id] #: contribution_action
128
142
  actions[lookahead] << action
129
143
  end
130
144
  actions
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ require "json"
7
+ require_relative "../verify/action_correspondence"
8
+
9
+ module Ibex
10
+ module LALR
11
+ # @rbs! type report_value = String | Symbol | Integer | bool | nil | IR::parser_action |
12
+ # Array[report_value] | Hash[Symbol, report_value]
13
+
14
+ # Diagnostic-only report for canonical-vs-LALR action differences. It is
15
+ # intentionally separate from normal construction because it enumerates a
16
+ # canonical LR(1) reference.
17
+ class InadequacyReport
18
+ # @rbs (IR::Automaton canonical, IR::Automaton target, ?max_pairs: Integer) -> void
19
+ def initialize(canonical, target, max_pairs: nil)
20
+ @canonical = canonical
21
+ @target = target
22
+ @max_pairs = max_pairs
23
+ end
24
+
25
+ # @rbs () -> Hash[Symbol, report_value]
26
+ def to_h
27
+ result = Verify::ActionCorrespondence.new(@canonical, @target, max_pairs: @max_pairs).verify
28
+ {
29
+ algorithm: @target.algorithm,
30
+ canonical_algorithm: @canonical.algorithm,
31
+ explored_pairs: result.explored,
32
+ truncated: result.truncated,
33
+ differences: result.differences.map do |difference|
34
+ {
35
+ kind: difference.kind, canonical_state: difference.canonical_state,
36
+ target_state: difference.target_state, symbol: difference.symbol,
37
+ canonical: difference.canonical, target: difference.target
38
+ }
39
+ end
40
+ }
41
+ end
42
+
43
+ # @rbs () -> String
44
+ def to_json(*)
45
+ "#{JSON.pretty_generate(to_h)}\n"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ # steep:ignore:end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ require "set"
7
+
8
+ module Ibex
9
+ module LALR
10
+ # Recomputes item lookaheads on an already-built LR(0) automaton. This is
11
+ # Phase 4 of direct IELR and is also useful as an independent cross-check
12
+ # for DirectLookaheads.
13
+ class LookaheadPropagation
14
+ EMPTY = Array.new(0).freeze #: Array[Integer]
15
+
16
+ attr_reader :propagation_edge_count #: Integer
17
+
18
+ # @rbs (IR::Grammar grammar, Analysis::Sets sets, Array[core_set] states, transitions transitions,
19
+ # seeds: Array[[Integer, item_core, Integer]]) -> Array[packed_items]
20
+ def initialize(grammar, sets, states, transitions, seeds:)
21
+ @grammar = grammar
22
+ @sets = sets
23
+ @states = states
24
+ @transitions = transitions
25
+ @seeds = seeds
26
+ @productions_by_lhs = grammar.productions.group_by(&:lhs)
27
+ end
28
+
29
+ # @rbs () -> Array[packed_items]
30
+ def build
31
+ lookaheads = @states.map { |items| items.to_h { |item| [item, Set.new] } }
32
+ edges = Hash.new { |hash, key| hash[key] = [] }
33
+ @states.each_with_index do |items, state_id|
34
+ items.each do |production_id, dot|
35
+ add_transition(edges, state_id, production_id, dot)
36
+ add_closure(edges, lookaheads, state_id, production_id, dot)
37
+ end
38
+ end
39
+ @seeds.each { |state_id, item, token| lookaheads.fetch(state_id).fetch(item) << token }
40
+ @propagation_edge_count = edges.values.sum(&:length)
41
+ propagate(lookaheads, edges)
42
+ lookaheads
43
+ end
44
+
45
+ private
46
+
47
+ # @rbs (Hash[Object, Array[Object]], Integer, Integer, Integer) -> void
48
+ def add_transition(edges, state_id, production_id, dot)
49
+ symbol_id = rhs_for(production_id)[dot]
50
+ return unless symbol_id
51
+
52
+ target = @transitions.fetch(state_id).fetch(symbol_id)
53
+ edges[[state_id, production_id, dot]] << [target, production_id, dot + 1]
54
+ end
55
+
56
+ # @rbs (Hash[Object, Array[Object]], Array[packed_items], Integer, Integer, Integer) -> void
57
+ def add_closure(edges, lookaheads, state_id, production_id, dot)
58
+ rhs = rhs_for(production_id)
59
+ symbol = @grammar.symbol_by_id(rhs[dot])
60
+ return unless symbol&.nonterminal?
61
+
62
+ suffix = rhs.drop(dot + 1)
63
+ spontaneous = terminal_ids(@sets.first_of_sequence(suffix))
64
+ @productions_by_lhs.fetch(symbol.id, []).each do |production|
65
+ item = [production.id, 0].freeze
66
+ lookaheads.fetch(state_id).fetch(item).merge(spontaneous)
67
+ edges[[state_id, production_id, dot]] << [state_id, production.id, 0] if
68
+ @sets.sequence_nullable?(suffix)
69
+ end
70
+ end
71
+
72
+ # @rbs (Array[packed_items], Hash[Object, Array[Object]]) -> void
73
+ def propagate(lookaheads, edges)
74
+ queue = @seeds.map { |state_id, (production_id, dot), _token| [state_id, production_id, dot] }
75
+ queue.concat(lookaheads.each_with_index.flat_map do |items, state_id|
76
+ items.filter_map { |(production_id, dot), values| [state_id, production_id, dot] unless values.empty? }
77
+ end)
78
+ queued = queue.to_h { |node| [node, true] }
79
+ cursor = 0
80
+ while cursor < queue.length
81
+ source = queue.fetch(cursor)
82
+ cursor += 1
83
+ queued.delete(source)
84
+ source_set = lookaheads.fetch(source[0]).fetch([source[1], source[2]])
85
+ edges.fetch(source, EMPTY).each do |target|
86
+ target_set = lookaheads.fetch(target[0]).fetch([target[1], target[2]])
87
+ previous_size = target_set.size
88
+ target_set.merge(source_set)
89
+ next if target_set.size == previous_size || queued[target]
90
+
91
+ queue << target
92
+ queued[target] = true
93
+ end
94
+ end
95
+ end
96
+
97
+ # @rbs (Integer bits) -> Array[Integer]
98
+ def terminal_ids(bits)
99
+ @grammar.terminals.filter_map { |terminal| terminal.id if bits.anybits?(1 << terminal.id) }
100
+ end
101
+
102
+ # @rbs (Integer production_id) -> Array[Integer]
103
+ def rhs_for(production_id)
104
+ return [@grammar.symbol(@grammar.starts.fetch(-production_id - 1)).id] if production_id.negative?
105
+
106
+ @grammar.productions.fetch(production_id).rhs
107
+ end
108
+ end
109
+ end
110
+ end
111
+ # steep:ignore:end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ require "set"
7
+
8
+ module Ibex
9
+ module LALR
10
+ # Constructs only the LR(0) core collection. Keeping this separate from
11
+ # lookahead propagation makes the direct IELR phases reusable and gives
12
+ # them a stable, canonical-free input representation.
13
+ class LR0Collection
14
+ AUGMENTED_PRODUCTION = -1 #: Integer
15
+
16
+ # @rbs @grammar: IR::Grammar
17
+ # @rbs @productions_by_lhs: Hash[Integer, Array[IR::Production]]
18
+ # @rbs @starts: Array[String]
19
+ # @rbs @augmented_rhs: Hash[Integer, Array[Integer]]
20
+ # @rbs @production_rhs: Array[Array[Integer]]
21
+ # @rbs @item_key_stride: Integer
22
+
23
+ # @rbs (IR::Grammar grammar, ?starts: Array[String]?) -> void
24
+ def initialize(grammar, starts: nil)
25
+ @grammar = grammar
26
+ @starts = (starts || grammar.starts).dup
27
+ raise ArgumentError, "starts must be a nonempty subset of grammar starts" if
28
+ @starts.empty? || (@starts - grammar.starts).any?
29
+
30
+ @productions_by_lhs = grammar.productions.group_by(&:lhs)
31
+ @production_rhs = grammar.productions.map(&:rhs).freeze
32
+ @augmented_production_ids = @starts.map { |name| AUGMENTED_PRODUCTION - grammar.starts.index(name) }
33
+ @augmented_rhs = @starts.each_with_index.to_h do |name, index|
34
+ symbol = grammar.symbol(name) || raise(Ibex::Error, "missing start symbol #{name}")
35
+ [@augmented_production_ids.fetch(index), [symbol.id].freeze]
36
+ end.freeze
37
+ @item_key_stride = [*@production_rhs, *@augmented_rhs.values].map(&:length).max.to_i + 1
38
+ end
39
+
40
+ # @rbs () -> [Array[core_set], transitions]
41
+ def build
42
+ states = @starts.map { |name| closure(Set[[augmented_production(@starts.index(name)), 0]]) }
43
+ transitions = [] #: transitions
44
+ indexes = {}
45
+ states.each_with_index { |items, index| indexes[item_key(items)] = index }
46
+ cursor = 0
47
+ while cursor < states.length
48
+ transitions[cursor] = {}
49
+ shifted_kernels(states.fetch(cursor)).keys.sort.each do |symbol_id|
50
+ target = closure(shifted_kernels(states.fetch(cursor)).fetch(symbol_id))
51
+ target_id = indexes[item_key(target)] ||= begin
52
+ states << target
53
+ states.length - 1
54
+ end
55
+ transitions.fetch(cursor)[symbol_id] = target_id
56
+ end
57
+ cursor += 1
58
+ end
59
+ [states, transitions]
60
+ end
61
+
62
+ # @rbs (Integer index) -> Integer
63
+ def augmented_production(index)
64
+ @augmented_production_ids.fetch(index)
65
+ end
66
+
67
+ # @rbs (Integer production_id) -> Array[Integer]
68
+ def rhs_for(production_id)
69
+ return @augmented_rhs.fetch(production_id) if production_id.negative?
70
+
71
+ @production_rhs.fetch(production_id)
72
+ end
73
+
74
+ # @rbs (Integer production_id) -> Integer
75
+ def lhs_for(production_id)
76
+ return rhs_for(production_id).fetch(0) if production_id.negative?
77
+
78
+ @grammar.productions.fetch(production_id).lhs
79
+ end
80
+
81
+ # @rbs (core_set items) -> Hash[Integer, core_set]
82
+ def shifted_kernels(items)
83
+ kernels = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[Integer, core_set]
84
+ items.each do |production_id, dot|
85
+ symbol_id = rhs_for(production_id)[dot]
86
+ kernels[symbol_id] << [production_id, dot + 1].freeze if symbol_id
87
+ end
88
+ kernels
89
+ end
90
+
91
+ private
92
+
93
+ # @rbs (core_set seed) -> core_set
94
+ def closure(seed)
95
+ items = seed.dup
96
+ queue = seed.to_a
97
+ cursor = 0
98
+ while cursor < queue.length
99
+ production_id, dot = queue.fetch(cursor)
100
+ cursor += 1
101
+ symbol = @grammar.symbol_by_id(rhs_for(production_id)[dot])
102
+ next unless symbol&.nonterminal?
103
+
104
+ @productions_by_lhs.fetch(symbol.id, []).each do |production|
105
+ item = [production.id, 0].freeze
106
+ queue << item if items.add?(item)
107
+ end
108
+ end
109
+ items
110
+ end
111
+
112
+ # @rbs (core_set items) -> Array[Integer]
113
+ def item_key(items)
114
+ items.map do |production_id, dot|
115
+ ((production_id + @grammar.starts.length) * @item_key_stride) + dot
116
+ end.sort
117
+ end
118
+ end
119
+ end
120
+ end
121
+ # steep:ignore:end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ module Ibex
7
+ module LALR
8
+ # Optional post-resolution compaction. It is intentionally off by
9
+ # default because state numbers are part of diagnostics and golden output.
10
+ module UnreachableStates
11
+ module_function
12
+
13
+ # @rbs (Array[IR::AutomatonState] states, Array[Integer] start_states) ->
14
+ # [Array[IR::AutomatonState], Hash[Integer, Integer]]
15
+ def remove(states, start_states)
16
+ reachable = reachable_states(states, start_states)
17
+ mapping = reachable.each_with_index.to_h { |old_id, new_id| [old_id, new_id] }
18
+ return [states, mapping] if reachable.length == states.length
19
+
20
+ compacted = reachable.map do |old_id|
21
+ state = states.fetch(old_id)
22
+ transitions = remap_edges(state.transitions, mapping)
23
+ gotos = remap_edges(state.gotos, mapping)
24
+ actions = state.actions.transform_values { |action| remap_action(action, mapping) }
25
+ default_action = remap_action(state.default_action, mapping)
26
+ conflicts = state.conflicts.map { |conflict| remap_conflict(conflict, mapping) }
27
+ IR::AutomatonState.new(
28
+ id: mapping.fetch(old_id), items: state.items, transitions: transitions,
29
+ actions: actions, gotos: gotos, default_action: default_action, conflicts: conflicts
30
+ )
31
+ end
32
+ [compacted, mapping]
33
+ end
34
+
35
+ # @rbs (Array[IR::AutomatonState], Array[Integer]) -> Array[Integer]
36
+ def reachable_states(states, start_states)
37
+ visited = {}
38
+ queue = start_states.dup
39
+ until queue.empty?
40
+ state_id = queue.shift
41
+ next if visited[state_id]
42
+
43
+ visited[state_id] = true
44
+ state = states.fetch(state_id)
45
+ state.gotos.each_value { |target| queue << target }
46
+ state.actions.each_value do |action|
47
+ queue << action[:state] if action[:type] == :shift
48
+ end
49
+ end
50
+ visited.keys.sort
51
+ end
52
+
53
+ # @rbs (Hash[Integer, Integer], Hash[Integer, Integer]) -> Hash[Integer, Integer]
54
+ def remap_edges(edges, mapping)
55
+ edges.each_with_object({}) do |(symbol_id, target), result|
56
+ result[symbol_id] = mapping[target] if mapping.key?(target)
57
+ end
58
+ end
59
+
60
+ # @rbs (IR::parser_action?, Hash[Integer, Integer]) -> IR::parser_action?
61
+ def remap_action(action, mapping)
62
+ return nil unless action
63
+ return action unless action[:type] == :shift
64
+ return { type: :shift, state: mapping.fetch(action[:state]) } if mapping.key?(action[:state])
65
+
66
+ { type: :error }
67
+ end
68
+
69
+ # @rbs (IR::conflict, Hash[Integer, Integer]) -> IR::conflict
70
+ def remap_conflict(conflict, mapping)
71
+ result = conflict.dup
72
+ if result[:type] == :shift_reduce && mapping.key?(result[:shift_to])
73
+ result[:shift_to] = mapping.fetch(result[:shift_to])
74
+ end
75
+ result
76
+ end
77
+ end
78
+ end
79
+ end
80
+ # steep:ignore:end