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,138 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # steep:ignore:start
5
+
6
+ module Ibex
7
+ module Verify
8
+ # @rbs! type correspondence_action = IR::parser_action
9
+
10
+ # Compares resolved ACTION/GOTO behavior over all paired viable prefixes.
11
+ class ActionCorrespondence
12
+ # @rbs skip
13
+ Difference = Struct.new(:kind, :canonical_state, :target_state, :symbol, :canonical, :target,
14
+ keyword_init: true) do
15
+ def initialize(**values)
16
+ super
17
+ freeze
18
+ end
19
+ end
20
+ # @rbs skip
21
+ Result = Struct.new(:differences, :explored, :truncated, keyword_init: true) do
22
+ def ok?
23
+ differences.empty? && !truncated
24
+ end
25
+ end
26
+
27
+ # @rbs!
28
+ # class Difference < Struct[Symbol | Integer? | correspondence_action?]
29
+ # attr_accessor kind: Symbol
30
+ # attr_accessor canonical_state: Integer
31
+ # attr_accessor target_state: Integer
32
+ # attr_accessor symbol: Integer?
33
+ # attr_accessor canonical: correspondence_action?
34
+ # attr_accessor target: correspondence_action?
35
+ # def self.new: (kind: Symbol, canonical_state: Integer, target_state: Integer,
36
+ # symbol: Integer?, canonical: correspondence_action?, target: correspondence_action?) -> instance
37
+ # end
38
+ # class Result < Struct[Array[Difference] | Integer | bool]
39
+ # attr_accessor differences: Array[Difference]
40
+ # attr_accessor explored: Integer
41
+ # attr_accessor truncated: bool
42
+ # def self.new: (differences: Array[Difference], explored: Integer, truncated: bool) -> instance
43
+ # def ok?: () -> bool
44
+ # end
45
+
46
+ # @rbs (IR::Automaton canonical, IR::Automaton target, ?max_pairs: Integer) -> void
47
+ def initialize(canonical, target, max_pairs: nil)
48
+ @canonical = canonical
49
+ @target = target
50
+ @max_pairs = max_pairs || [canonical.states.length * 8, 1].max
51
+ end
52
+
53
+ # @rbs () -> Result
54
+ def verify
55
+ queue = @canonical.entry_states.map do |name, canonical_state|
56
+ [canonical_state, @target.entry_states[name]]
57
+ end
58
+ seen = {}
59
+ differences = []
60
+ truncated = false
61
+ until queue.empty?
62
+ canonical_id, target_id = queue.shift
63
+ key = [canonical_id, target_id]
64
+ next if seen[key]
65
+
66
+ if seen.length >= @max_pairs
67
+ truncated = true
68
+ break
69
+ end
70
+ seen[key] = true
71
+ canonical_state = @canonical.states[canonical_id]
72
+ target_state = @target.states[target_id]
73
+ unless canonical_state && target_state
74
+ differences << Difference.new(
75
+ kind: :state, canonical_state: canonical_id, target_state: target_id,
76
+ symbol: nil, canonical: canonical_state, target: target_state
77
+ )
78
+ next
79
+ end
80
+ compare_terminals(canonical_id, target_id, canonical_state, target_state, differences, queue)
81
+ compare_gotos(canonical_id, target_id, canonical_state, target_state, differences, queue)
82
+ end
83
+ Result.new(differences: differences.freeze, explored: seen.length, truncated: truncated)
84
+ end
85
+
86
+ private
87
+
88
+ # @rbs (Integer, Integer, IR::AutomatonState, IR::AutomatonState,
89
+ # Array[Difference], Array[Array[Integer]]) -> void
90
+ def compare_terminals(canonical_id, target_id, canonical_state, target_state, differences, queue)
91
+ @canonical.grammar.terminals.each do |terminal|
92
+ left = effective_action(canonical_state, terminal.id)
93
+ right = effective_action(target_state, terminal.id)
94
+ if action_signature(left) != action_signature(right)
95
+ differences << Difference.new(
96
+ kind: :action, canonical_state: canonical_id, target_state: target_id,
97
+ symbol: terminal.id, canonical: left, target: right
98
+ )
99
+ end
100
+ queue << [left[:state], right[:state]] if left[:type] == :shift && right[:type] == :shift
101
+ end
102
+ end
103
+
104
+ # @rbs (Integer, Integer, IR::AutomatonState, IR::AutomatonState,
105
+ # Array[Difference], Array[Array[Integer]]) -> void
106
+ def compare_gotos(canonical_id, target_id, canonical_state, target_state, differences, queue)
107
+ @canonical.grammar.nonterminals.each do |nonterminal|
108
+ left = canonical_state.gotos[nonterminal.id]
109
+ right = target_state.gotos[nonterminal.id]
110
+ if left.nil? != right.nil?
111
+ differences << Difference.new(
112
+ kind: :goto, canonical_state: canonical_id, target_state: target_id,
113
+ symbol: nonterminal.id, canonical: left, target: right
114
+ )
115
+ elsif left && right
116
+ queue << [left, right]
117
+ end
118
+ end
119
+ end
120
+
121
+ # @rbs (IR::AutomatonState, Integer) -> IR::parser_action
122
+ def effective_action(state, token_id)
123
+ state.actions.fetch(token_id, state.default_action || { type: :error })
124
+ end
125
+
126
+ # @rbs (IR::parser_action) -> Array[Object]
127
+ def action_signature(action)
128
+ case action[:type]
129
+ when :shift then [:shift]
130
+ when :reduce then [:reduce, action[:production]]
131
+ when :accept then [:accept]
132
+ else [:error]
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ # steep:ignore:end
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "set"
5
+ require_relative "../lalr/conflict"
6
+ require_relative "../lalr/default_reductions"
7
+ require_relative "../lalr/on_error_reductions"
8
+
9
+ module Ibex
10
+ module Verify
11
+ # @rbs! type witness_status = :accepted | :error | :missing_entry | :shifted
12
+
13
+ # Compares canonical and submitted parser acceptance over a bounded set of
14
+ # terminal sequences. This is a semantic witness, not a proof of language
15
+ # equivalence: the reference collection is complete within its budgets and
16
+ # the token enumeration is deliberately finite.
17
+ class LanguageWitness
18
+ class Difference
19
+ attr_reader :entry #: String
20
+ attr_reader :tokens #: Array[String]
21
+ attr_reader :canonical #: witness_status
22
+ attr_reader :target #: witness_status
23
+
24
+ # @rbs (entry: String, tokens: Array[String], canonical: witness_status,
25
+ # target: witness_status) -> void
26
+ def initialize(entry:, tokens:, canonical:, target:)
27
+ @entry = entry.freeze
28
+ @tokens = tokens.dup.freeze
29
+ @canonical = canonical
30
+ @target = target
31
+ freeze
32
+ end
33
+ end
34
+
35
+ class Result
36
+ attr_reader :differences #: Array[Difference]
37
+ attr_reader :explored #: Integer
38
+ attr_reader :truncated #: bool
39
+ attr_reader :max_tokens #: Integer
40
+ attr_reader :max_cases #: Integer
41
+
42
+ # @rbs (differences: Array[Difference], explored: Integer, truncated: bool,
43
+ # max_tokens: Integer, max_cases: Integer) -> void
44
+ def initialize(differences:, explored:, truncated:, max_tokens:, max_cases:)
45
+ @differences = differences.dup.freeze
46
+ @explored = explored
47
+ @truncated = truncated
48
+ @max_tokens = max_tokens
49
+ @max_cases = max_cases
50
+ freeze
51
+ end
52
+
53
+ # @rbs () -> bool
54
+ def ok?
55
+ differences.empty? && !truncated
56
+ end
57
+ end
58
+
59
+ # @rbs (IR::Grammar grammar, IR::Automaton target, ?max_tokens: Integer,
60
+ # ?max_cases: Integer, ?max_states: Integer, ?max_items: Integer) -> void
61
+ def initialize(grammar, target, max_tokens: 6, max_cases: 10_000, max_states: 100_000,
62
+ max_items: 1_000_000)
63
+ raise ArgumentError, "max_tokens must be nonnegative" if max_tokens.negative?
64
+ raise ArgumentError, "max_cases must be positive" unless max_cases.positive?
65
+
66
+ @grammar = grammar
67
+ @target = target
68
+ @max_tokens = max_tokens
69
+ @max_cases = max_cases
70
+ @canonical = CanonicalMachine.new(
71
+ grammar, max_states: max_states, max_items: max_items
72
+ )
73
+ end
74
+
75
+ # @rbs () -> Result
76
+ def verify
77
+ differences = [] #: Array[Difference]
78
+ explored = 0
79
+ truncated = false
80
+ token_ids = input_tokens.map(&:id)
81
+
82
+ @grammar.starts.each do |entry|
83
+ explored, truncated = verify_entry(entry, token_ids, differences, explored)
84
+ break if truncated
85
+ end
86
+
87
+ Result.new(
88
+ differences: differences.freeze, explored: explored, truncated: truncated,
89
+ max_tokens: @max_tokens, max_cases: @max_cases
90
+ )
91
+ end
92
+
93
+ private
94
+
95
+ # @rbs () -> Array[IR::GrammarSymbol]
96
+ def input_tokens
97
+ @grammar.terminals.reject { |terminal| ["$eof", "error"].include?(terminal.name) }
98
+ end
99
+
100
+ # @rbs (String, Array[Integer], Array[Difference], Integer) -> [Integer, bool]
101
+ def verify_entry(entry, token_ids, differences, explored)
102
+ queue = [[]] #: Array[Array[Integer]]
103
+ until queue.empty?
104
+ tokens = queue.shift
105
+ return [explored, true] if explored >= @max_cases
106
+
107
+ explored += 1
108
+ canonical = @canonical.simulate(entry, tokens)
109
+ target = simulate_target(entry, tokens)
110
+ if canonical != target
111
+ differences << Difference.new(
112
+ entry: entry, tokens: tokens.map { |id| token_name(id) },
113
+ canonical: canonical, target: target
114
+ )
115
+ return [explored, false]
116
+ end
117
+
118
+ # Keep the worklist breadth-first so a reported witness is as short
119
+ # as possible for each entry.
120
+ queue.concat(token_ids.map { |token_id| tokens + [token_id] }) if
121
+ tokens.length < @max_tokens
122
+ end
123
+ [explored, false]
124
+ end
125
+
126
+ # @rbs (Integer id) -> String
127
+ def token_name(id)
128
+ @grammar.symbol_by_id(id)&.name || raise(Ibex::Error, "missing terminal #{id}")
129
+ end
130
+
131
+ # @rbs (String entry, Array[Integer]) -> witness_status
132
+ def simulate_target(entry, tokens)
133
+ initial = @target.entry_states[entry]
134
+ return :missing_entry unless initial
135
+
136
+ machine = Machine.new(@target.states, @grammar)
137
+ machine.simulate(initial, tokens)
138
+ end
139
+
140
+ # Canonical collection is intentionally built here rather than through
141
+ # the production construction entry point, keeping this witness
142
+ # independent from construction code.
143
+ class CanonicalMachine
144
+ # @rbs (IR::Grammar grammar, max_states: Integer, max_items: Integer) -> void
145
+ def initialize(grammar, max_states:, max_items:)
146
+ collection = ReferenceCollection.new(
147
+ grammar, max_states: max_states, max_items: max_items
148
+ ).build(:lr1)
149
+ @grammar = grammar
150
+ @states = build_states(collection)
151
+ @entry_states = grammar.starts.each_with_index.to_h
152
+ end
153
+
154
+ # @rbs (String entry, Array[Integer]) -> witness_status
155
+ def simulate(entry, tokens)
156
+ state = @entry_states.fetch(entry)
157
+ Machine.new(@states, @grammar).simulate(state, tokens)
158
+ end
159
+
160
+ private
161
+
162
+ # @rbs (ReferenceCollection::Collection) -> Array[IR::AutomatonState]
163
+ def build_states(collection)
164
+ states = collection.states.each_with_index.map do |raw_items, state_id|
165
+ build_state(collection, raw_items, state_id)
166
+ end
167
+ states = LALR::OnErrorReductions.apply(@grammar, states)
168
+ LALR::DefaultReductions.apply(states, terminal_ids: @grammar.terminals.map(&:id))
169
+ end
170
+
171
+ # @rbs (ReferenceCollection::Collection, Set[Array[Integer]], Integer) -> IR::AutomatonState
172
+ def build_state(collection, raw_items, state_id)
173
+ item_map = Hash.new { |hash, key| hash[key] = Set.new } #: Hash[[Integer, Integer], Set[Integer]]
174
+ raw_items.each do |raw_item|
175
+ production = raw_item.fetch(0) #: Integer
176
+ dot = raw_item.fetch(1) #: Integer
177
+ lookahead = raw_item.fetch(2) #: Integer
178
+ item_map[[production, dot]] << lookahead
179
+ end
180
+ items = item_map.sort.map do |(production, dot), lookaheads|
181
+ IR::AutomatonItem.new(production: production, dot: dot, lookaheads: lookaheads.to_a)
182
+ end
183
+ transitions = collection.transitions.fetch(state_id)
184
+ actions = resolve_actions(items, transitions)
185
+ gotos = {} #: Hash[Integer, Integer]
186
+ transitions.each do |symbol_id, target|
187
+ gotos[symbol_id] = target if @grammar.symbol_by_id(symbol_id)&.nonterminal?
188
+ end
189
+ IR::AutomatonState.new(
190
+ id: state_id, items: items, transitions: transitions,
191
+ actions: actions, gotos: gotos
192
+ )
193
+ end
194
+
195
+ # @rbs (Array[IR::AutomatonItem], Hash[Integer, Integer]) -> Hash[Integer, IR::parser_action]
196
+ def resolve_actions(items, transitions)
197
+ candidates = transition_candidates(transitions)
198
+ completed_candidates(items, candidates)
199
+ resolver = LALR::ConflictResolver.new(@grammar)
200
+ candidates.keys.sort.to_h do |token_id|
201
+ action, = resolver.resolve(token_id, candidates.fetch(token_id))
202
+ [token_id, action || { type: :error }]
203
+ end
204
+ end
205
+
206
+ # @rbs (Hash[Integer, Integer]) -> Hash[Integer, Array[IR::parser_action]]
207
+ def transition_candidates(transitions)
208
+ candidates = Hash.new { |hash, key| hash[key] = [] } #: Hash[Integer, Array[IR::parser_action]]
209
+ transitions.each do |symbol_id, target|
210
+ next unless @grammar.symbol_by_id(symbol_id)&.terminal?
211
+
212
+ candidates[symbol_id] << { type: :shift, state: target }
213
+ end
214
+ candidates
215
+ end
216
+
217
+ # @rbs (Array[IR::AutomatonItem], Hash[Integer, Array[IR::parser_action]]) -> void
218
+ def completed_candidates(items, candidates)
219
+ items.each do |item|
220
+ next unless item.dot == rhs_for(item.production).length
221
+
222
+ action = if item.production.negative?
223
+ { type: :accept } #: IR::accept_action
224
+ else
225
+ { type: :reduce, production: item.production } #: IR::reduce_action
226
+ end
227
+ item.lookaheads.each { |lookahead| candidates[lookahead] << action }
228
+ end
229
+ end
230
+
231
+ # @rbs (Integer production_id) -> Array[Integer]
232
+ def rhs_for(production_id)
233
+ return [@grammar.symbol(@grammar.starts.fetch(-production_id - 1)).id] if
234
+ production_id.negative?
235
+
236
+ @grammar.productions.fetch(production_id).rhs
237
+ end
238
+ end
239
+
240
+ # A stack machine shared by the canonical reference and submitted IR.
241
+ class Machine
242
+ # @rbs (Array[IR::AutomatonState] states, IR::Grammar grammar) -> void
243
+ def initialize(states, grammar)
244
+ @states = states
245
+ @grammar = grammar
246
+ @eof = grammar.symbol("$eof") || raise(Ibex::Error, "missing $eof terminal")
247
+ end
248
+
249
+ # @rbs (Integer initial, Array[Integer]) -> witness_status
250
+ def simulate(initial, tokens)
251
+ stack = [initial]
252
+ tokens.each do |token_id|
253
+ status = consume(stack, token_id)
254
+ return status unless status == :shifted
255
+ end
256
+ loop do
257
+ status = consume(stack, @eof.id)
258
+ return :accepted if status == :accepted
259
+ return :error unless status == :shifted
260
+ end
261
+ end
262
+
263
+ private
264
+
265
+ # @rbs (Array[Integer], Integer) -> witness_status
266
+ def consume(stack, token_id)
267
+ steps = 0
268
+ loop do
269
+ steps += 1
270
+ return :error if steps > 10_000
271
+
272
+ state = @states.fetch(stack.last)
273
+ action = state.actions.fetch(token_id, state.default_action || { type: :error })
274
+ case action.fetch(:type)
275
+ when :shift
276
+ stack << action.fetch(:state)
277
+ return :shifted
278
+ when :reduce
279
+ production = @grammar.productions.fetch(action.fetch(:production))
280
+ return :error if production.rhs.length >= stack.length
281
+
282
+ production.rhs.length.times { stack.pop }
283
+ goto = @states.fetch(stack.last).gotos[production.lhs]
284
+ return :error unless goto
285
+
286
+ stack << goto
287
+ when :accept then return :accepted
288
+ else return :error
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "set"
5
+
6
+ module Ibex
7
+ module Verify
8
+ # Independently derives canonical LR(1) and LR(0) item collections.
9
+ class ReferenceCollection
10
+ # Immutable canonical collection and its symbol transitions.
11
+ class Collection
12
+ attr_reader :states #: Array[Set[Array[Integer]]]
13
+ attr_reader :transitions #: Array[Hash[Integer, Integer]]
14
+
15
+ # @rbs (states: Array[Set[Array[Integer]]], transitions: Array[Hash[Integer, Integer]]) -> void
16
+ def initialize(states:, transitions:)
17
+ @states = states
18
+ @transitions = transitions
19
+ freeze
20
+ end
21
+ end
22
+
23
+ # @rbs (IR::Grammar grammar, ?max_states: Integer, ?max_items: Integer) -> void
24
+ def initialize(grammar, max_states: 100_000, max_items: 1_000_000)
25
+ raise ArgumentError, "max_states must be positive" unless max_states.positive?
26
+ raise ArgumentError, "max_items must be positive" unless max_items.positive?
27
+
28
+ @grammar = grammar
29
+ @max_states = max_states
30
+ @max_items = max_items
31
+ @sets = Analysis::Sets.new(grammar)
32
+ @productions = grammar.productions.group_by(&:lhs)
33
+ @item_count = 0
34
+ end
35
+
36
+ # @rbs (Symbol kind) -> Collection
37
+ def build(kind)
38
+ raise ArgumentError, "kind must be :lr0 or :lr1" unless %i[lr0 lr1].include?(kind)
39
+
40
+ @item_count = 0
41
+ seeds = @grammar.starts.map.with_index do |name, index|
42
+ raise Ibex::Error, "(verify):1:1: missing start symbol #{name}" unless @grammar.symbol(name)
43
+
44
+ item = kind == :lr1 ? [-index - 1, 0, eof_id] : [-index - 1, 0]
45
+ account_item
46
+ closure(Set[item], kind)
47
+ end
48
+ collection(seeds, kind)
49
+ end
50
+
51
+ private
52
+
53
+ # @rbs (Array[Set[Array[Integer]]] seeds, Symbol kind) -> Collection
54
+ def collection(seeds, kind)
55
+ states = [] #: Array[Set[Array[Integer]]]
56
+ transitions = [] #: Array[Hash[Integer, Integer]]
57
+ indexes = {} #: Hash[Array[Array[Integer]], Integer]
58
+ seeds.each { |seed| insert_state(states, indexes, seed) }
59
+ cursor = 0
60
+ while cursor < states.length
61
+ transitions[cursor] = {}
62
+ next_symbols(states.fetch(cursor)).each do |symbol_id|
63
+ target = go_to(states.fetch(cursor), symbol_id, kind)
64
+ target_id = insert_state(states, indexes, target)
65
+ transitions.fetch(cursor)[symbol_id] = target_id
66
+ end
67
+ cursor += 1
68
+ end
69
+ Collection.new(states: states.freeze, transitions: transitions.freeze).freeze
70
+ end
71
+
72
+ # @rbs (Array[Set[Array[Integer]]] states, Hash[Array[Array[Integer]], Integer] indexes,
73
+ # Set[Array[Integer]] state) -> Integer
74
+ def insert_state(states, indexes, state)
75
+ key = state.to_a.sort
76
+ existing = indexes[key]
77
+ return existing if existing
78
+ if states.length >= @max_states
79
+ raise BudgetExceeded.new(
80
+ "(verify):1:1: reference collection exceeds #{@max_states} states", bounds: bounds
81
+ )
82
+ end
83
+
84
+ states << state.freeze
85
+ indexes[key] = states.length - 1
86
+ end
87
+
88
+ # @rbs (Set[Array[Integer]] seed, Symbol kind) -> Set[Array[Integer]]
89
+ def closure(seed, kind)
90
+ items = seed.dup
91
+ queue = seed.to_a
92
+ cursor = 0
93
+ while cursor < queue.length
94
+ item = queue.fetch(cursor)
95
+ cursor += 1
96
+ production_id = item.fetch(0)
97
+ dot = item.fetch(1)
98
+ lookahead = item[2]
99
+ symbol_id = rhs_for(production_id)[dot]
100
+ symbol = @grammar.symbol_by_id(symbol_id)
101
+ next unless symbol&.nonterminal?
102
+
103
+ productions = @productions.fetch(symbol.id) do
104
+ [] #: Array[IR::Production]
105
+ end
106
+ productions.each do |production|
107
+ if kind == :lr1
108
+ closure_lookaheads(production_id, dot, lookahead).each do |token|
109
+ add_item(items, queue, [production.id, 0, token])
110
+ end
111
+ else
112
+ add_item(items, queue, [production.id, 0])
113
+ end
114
+ end
115
+ end
116
+ items
117
+ end
118
+
119
+ # @rbs (Set[Array[Integer]] state, Integer symbol_id, Symbol kind) -> Set[Array[Integer]]
120
+ def go_to(state, symbol_id, kind)
121
+ moved = state.each_with_object(Set.new) do |item, result|
122
+ production_id = item.fetch(0)
123
+ dot = item.fetch(1)
124
+ lookahead = item[2]
125
+ next unless rhs_for(production_id)[dot] == symbol_id
126
+
127
+ result << (kind == :lr1 ? [production_id, dot + 1, lookahead] : [production_id, dot + 1])
128
+ end
129
+ closure(moved, kind)
130
+ end
131
+
132
+ # @rbs (Set[Array[Integer]] items, Array[Array[Integer]] queue, Array[Integer] item) -> void
133
+ def add_item(items, queue, item)
134
+ return if items.include?(item)
135
+
136
+ account_item
137
+ items << item
138
+ queue << item
139
+ end
140
+
141
+ # @rbs () -> void
142
+ def account_item
143
+ if @item_count >= @max_items
144
+ raise BudgetExceeded.new(
145
+ "(verify):1:1: reference collection exceeds #{@max_items} items", bounds: bounds
146
+ )
147
+ end
148
+
149
+ @item_count += 1
150
+ end
151
+
152
+ # @rbs (Set[Array[Integer]] state) -> Array[Integer]
153
+ def next_symbols(state)
154
+ state.filter_map { |item| rhs_for(item.fetch(0))[item.fetch(1)] }.uniq.sort
155
+ end
156
+
157
+ # @rbs (Integer production_id, Integer dot, Integer? inherited) -> Array[Integer]
158
+ def closure_lookaheads(production_id, dot, inherited)
159
+ suffix = rhs_for(production_id).drop(dot + 1)
160
+ bits = @sets.first_of_sequence(suffix)
161
+ bits |= (1 << inherited) if inherited && @sets.sequence_nullable?(suffix)
162
+ @grammar.terminals.filter_map { |terminal| terminal.id if bits.anybits?(1 << terminal.id) }
163
+ end
164
+
165
+ # @rbs (Integer production_id) -> Array[Integer]
166
+ def rhs_for(production_id)
167
+ return [start_symbol(production_id).id] if production_id.negative?
168
+
169
+ @grammar.productions.fetch(production_id).rhs
170
+ end
171
+
172
+ # @rbs (Integer production_id) -> IR::GrammarSymbol
173
+ def start_symbol(production_id)
174
+ name = @grammar.starts.fetch(-production_id - 1)
175
+ @grammar.symbol(name) || raise(Ibex::Error, "(verify):1:1: missing start symbol #{name}")
176
+ end
177
+
178
+ # @rbs () -> Integer
179
+ def eof_id
180
+ symbol = @grammar.symbol("$eof")
181
+ symbol&.id || raise(Ibex::Error, "(verify):1:1: grammar has no $eof terminal")
182
+ end
183
+
184
+ # @rbs () -> Hash[Symbol, Integer]
185
+ def bounds
186
+ { max_states: @max_states, max_items: @max_items }
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Verify
6
+ # One stable verifier finding.
7
+ class Violation
8
+ attr_reader :id #: String
9
+ attr_reader :location #: String
10
+ attr_reader :message #: String
11
+
12
+ # @rbs (id: String, location: String, message: String) -> void
13
+ def initialize(id:, location:, message:)
14
+ @id = id.freeze
15
+ @location = location.freeze
16
+ @message = message.freeze
17
+ freeze
18
+ end
19
+
20
+ # @rbs () -> Hash[Symbol, String]
21
+ def to_h
22
+ { id: @id, location: @location, message: @message }.freeze
23
+ end
24
+ end
25
+
26
+ # Immutable independent-verification report.
27
+ class Result
28
+ # @rbs!
29
+ # type report_value = String | Integer | bool | Array[String] | Hash[Symbol, Integer]
30
+ # | Array[Hash[Symbol, String]]
31
+
32
+ attr_reader :algorithm #: String
33
+ attr_reader :strict #: bool
34
+ attr_reader :checks #: Array[String]
35
+ attr_reader :violations #: Array[Violation]
36
+ attr_reader :bounds #: Hash[Symbol, Integer]
37
+
38
+ # @rbs (algorithm: String, strict: bool, checks: Array[String], violations: Array[Violation],
39
+ # bounds: Hash[Symbol, Integer]) -> void
40
+ def initialize(algorithm:, strict:, checks:, violations:, bounds:)
41
+ @algorithm = algorithm.freeze
42
+ @strict = strict
43
+ @checks = checks.dup.freeze
44
+ @violations = violations.dup.freeze
45
+ @bounds = bounds.dup.freeze
46
+ freeze
47
+ end
48
+
49
+ # @rbs () -> bool
50
+ def valid? = @violations.empty?
51
+
52
+ # @rbs () -> Hash[Symbol, report_value]
53
+ def to_h
54
+ {
55
+ ibex_report: "verify", schema_version: 1, algorithm: @algorithm,
56
+ strict: @strict, checks: @checks, bounds: @bounds,
57
+ result: valid? ? "valid" : "invalid",
58
+ violations: @violations.map(&:to_h)
59
+ }.freeze
60
+ end
61
+ end
62
+
63
+ class BudgetExceeded < Ibex::Error
64
+ attr_reader :bounds #: Hash[Symbol, Integer]
65
+
66
+ # @rbs (String message, bounds: Hash[Symbol, Integer]) -> void
67
+ def initialize(message, bounds:)
68
+ @bounds = bounds.dup.freeze
69
+ super(message)
70
+ end
71
+ end
72
+ end
73
+ end