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,516 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module BisonImport
6
+ # Converts Bison declarations and productions into analysis-only Ibex
7
+ # source without parsing or executing C.
8
+ # rubocop:disable Metrics/ClassLength -- declaration and rule recovery share one positioned directive report.
9
+ class Importer
10
+ # @rbs!
11
+ # type alternative = { items: Array[String], precedence: String? }
12
+ # type rule = { lhs: String, alternatives: Array[alternative] }
13
+
14
+ DEFAULT_MAX_BYTES = 20 * 1024 * 1024 #: Integer
15
+ DEFAULT_MAX_TOKENS = 1_000_000 #: Integer
16
+ DEFAULT_MAX_RULES = 50_000 #: Integer
17
+ DEFAULT_MAX_ACTIONS = 100_000 #: Integer
18
+
19
+ # @rbs (String source, file: String, ?class_name: String?, ?max_bytes: Integer,
20
+ # ?max_tokens: Integer, ?max_rules: Integer, ?max_actions: Integer) -> void
21
+ def initialize(source, file:, class_name: nil, max_bytes: DEFAULT_MAX_BYTES,
22
+ max_tokens: DEFAULT_MAX_TOKENS, max_rules: DEFAULT_MAX_RULES,
23
+ max_actions: DEFAULT_MAX_ACTIONS)
24
+ @source = source
25
+ @file = file
26
+ @class_name = class_name
27
+ @max_bytes = positive_limit(max_bytes, :max_bytes)
28
+ @max_tokens = positive_limit(max_tokens, :max_tokens)
29
+ @max_rules = positive_limit(max_rules, :max_rules)
30
+ @max_actions = positive_limit(max_actions, :max_actions)
31
+ @directives = [] #: Array[Directive]
32
+ @actions = [] #: Array[Action]
33
+ @token_entries = [] #: Array[[String, String?]]
34
+ @terminal_names = {} #: Hash[String, String]
35
+ @nonterminal_names = {} #: Hash[String, String]
36
+ @precedence_levels = [] #: Array[[String, Array[String]]]
37
+ @starts = [] #: Array[String]
38
+ @expected_sr = nil #: Integer?
39
+ @expected_rr = nil #: Integer?
40
+ end
41
+
42
+ # @rbs () -> Result
43
+ def run
44
+ validate_source
45
+ declarations, grammar, grammar_line = split_sections
46
+ parse_declarations(declarations)
47
+ tokens = Tokenizer.new(grammar, start_line: grammar_line, max_tokens: @max_tokens).tokenize
48
+ register_nonterminals(tokens)
49
+ rules = parse_rules(tokens)
50
+ source = render_source(rules)
51
+ Result.new(
52
+ source: source, file: @file, class_name: resolved_class_name,
53
+ directives: @directives, actions: @actions, rule_count: rules.length,
54
+ bounds: bounds
55
+ )
56
+ end
57
+
58
+ private
59
+
60
+ # @rbs () -> void
61
+ def validate_source
62
+ source = @source #: String
63
+ if source.bytesize > @max_bytes
64
+ raise BudgetExceeded.new(
65
+ result: "budget_exhausted", phase: "input_bytes",
66
+ observed_bytes: source.bytesize, max_bytes: @max_bytes
67
+ )
68
+ end
69
+ raise Ibex::Error, "#{@file}:1:1: Bison grammar must be valid UTF-8" unless source.valid_encoding?
70
+ end
71
+
72
+ # @rbs () -> [String, String, Integer]
73
+ def split_sections
74
+ source = @source #: String
75
+ lines = source.lines
76
+ markers = [] #: Array[Integer]
77
+ percent_code = false
78
+ lines.each_with_index do |line, index|
79
+ percent_code = true if line.match?(/^\s*%\{/)
80
+ markers << index if !percent_code && line.match?(%r{^\s*%%(?:\s|/|$)})
81
+ percent_code = false if percent_code && line.match?(/%\}\s*$/)
82
+ break if markers.length == 2
83
+ end
84
+ raise Ibex::Error, "#{@file}:1:1: expected two Bison %% section markers" if markers.length < 2
85
+
86
+ first = markers.fetch(0)
87
+ second = markers.fetch(1)
88
+ header_lines = lines[0...first] #: Array[String]
89
+ grammar_lines = lines[(first + 1)...second] #: Array[String]
90
+ [header_lines.join, grammar_lines.join, first + 2]
91
+ end
92
+
93
+ # @rbs (String source) -> void
94
+ def parse_declarations(source)
95
+ chunks = declaration_chunks(source)
96
+ chunks.each do |chunk|
97
+ name = chunk.fetch(:name)
98
+ detail = chunk.fetch(:detail)
99
+ record_directive(name, chunk.fetch(:line), chunk.fetch(:column), detail)
100
+ case name
101
+ when "token" then parse_token_declaration(detail)
102
+ when "left", "right", "nonassoc", "precedence" then parse_precedence(name, detail)
103
+ when "start" then parse_start(detail)
104
+ when "expect" then @expected_sr = first_integer(detail)
105
+ when "expect-rr" then @expected_rr = first_integer(detail)
106
+ end
107
+ end
108
+ end
109
+
110
+ # @rbs (String source) -> Array[{ name: String, detail: String, line: Integer, column: Integer }]
111
+ def declaration_chunks(source)
112
+ chunks = [] #: Array[{ name: String, detail: String, line: Integer, column: Integer }]
113
+ current = nil #: { name: String, detail: String, line: Integer, column: Integer }?
114
+ in_percent_code = false
115
+ source.lines.each_with_index do |line, index|
116
+ if line.match?(/^\s*%\{/)
117
+ in_percent_code = true
118
+ next
119
+ end
120
+ if in_percent_code
121
+ in_percent_code = false if line.match?(/%\}\s*$/)
122
+ next
123
+ end
124
+
125
+ match = line.match(/^(\s*)%([A-Za-z][A-Za-z0-9_-]*)(.*)$/)
126
+ if match
127
+ chunks << current if current
128
+ current = {
129
+ name: match[2].to_s,
130
+ detail: match[3].to_s,
131
+ line: index + 1,
132
+ column: match[1].to_s.bytesize + 1
133
+ }
134
+ elsif current
135
+ current[:detail] = "#{current.fetch(:detail)}\n#{line}"
136
+ end
137
+ end
138
+ chunks << current if current
139
+ chunks
140
+ end
141
+
142
+ # @rbs (String detail) -> void
143
+ def parse_token_declaration(detail)
144
+ token_entries = @token_entries #: Array[[String, String?]]
145
+ current = nil #: String?
146
+ declaration_atoms(detail).each do |atom|
147
+ if identifier_atom?(atom)
148
+ token_entries << [current, nil] if current
149
+ current = atom
150
+ terminal_name(atom)
151
+ elsif current && atom.start_with?('"')
152
+ token_entries << [current, atom]
153
+ current = nil
154
+ end
155
+ end
156
+ token_entries << [current, nil] if current
157
+ end
158
+
159
+ # @rbs (String association, String detail) -> void
160
+ def parse_precedence(association, detail)
161
+ symbols = declaration_atoms(detail).select { |atom| identifier_atom?(atom) || literal_atom?(atom) }
162
+ precedence_levels = @precedence_levels #: Array[[String, Array[String]]]
163
+ precedence_levels << [association == "precedence" ? "%precedence" : association, symbols] unless symbols.empty?
164
+ end
165
+
166
+ # @rbs (String detail) -> void
167
+ def parse_start(detail)
168
+ symbol = declaration_atoms(detail).find { |atom| identifier_atom?(atom) }
169
+ starts = @starts #: Array[String]
170
+ starts << symbol if symbol
171
+ end
172
+
173
+ # @rbs (Array[Tokenizer::Token] tokens) -> Array[rule]
174
+ def parse_rules(tokens)
175
+ rules = [] #: Array[rule]
176
+ cursor = 0
177
+ while cursor < tokens.length
178
+ definition = next_lhs(tokens, cursor)
179
+ break unless definition
180
+
181
+ lhs_index, colon_index = definition
182
+ lhs = nonterminal_name(tokens.fetch(lhs_index).value)
183
+ cursor = colon_index + 1
184
+ alternatives, cursor = parse_alternatives(tokens, cursor)
185
+ rules << { lhs: lhs, alternatives: alternatives }
186
+ check_rule_budget(rules.length)
187
+ end
188
+ raise Ibex::Error, "#{@file}:1:1: Bison grammar section contains no productions" if rules.empty?
189
+
190
+ rules
191
+ end
192
+
193
+ # @rbs (Array[Tokenizer::Token] tokens) -> void
194
+ def register_nonterminals(tokens)
195
+ cursor = 0
196
+ while (definition = next_lhs(tokens, cursor))
197
+ lhs_index, colon_index = definition
198
+ nonterminal_name(tokens.fetch(lhs_index).value)
199
+ cursor = colon_index + 1
200
+ end
201
+ end
202
+
203
+ # @rbs (Array[Tokenizer::Token] tokens, Integer cursor) -> [Integer, Integer]?
204
+ def next_lhs(tokens, cursor)
205
+ while cursor < tokens.length
206
+ definition = lhs_definition_at(tokens, cursor)
207
+ return definition if definition
208
+
209
+ cursor += 1
210
+ end
211
+ end
212
+
213
+ # Bison permits a named reference between an LHS and its colon:
214
+ # `expression[result]: ...`.
215
+ # @rbs (Array[Tokenizer::Token] tokens, Integer cursor) -> [Integer, Integer]?
216
+ def lhs_definition_at(tokens, cursor)
217
+ return unless tokens[cursor]&.type == :symbol
218
+
219
+ colon = cursor + 1
220
+ colon += 1 while tokens[colon]&.type == :tag
221
+ [cursor, colon] if tokens[colon]&.type == :colon
222
+ end
223
+
224
+ # @rbs (Array[Tokenizer::Token] tokens, Integer cursor) -> [Array[alternative], Integer]
225
+ def parse_alternatives(tokens, cursor)
226
+ alternatives = [] #: Array[alternative]
227
+ current = { items: [], precedence: nil } #: alternative
228
+ while cursor < tokens.length
229
+ token = tokens.fetch(cursor)
230
+ if lhs_definition_at(tokens, cursor)
231
+ alternatives << current
232
+ return [alternatives, cursor]
233
+ end
234
+
235
+ case token.type
236
+ when :pipe
237
+ alternatives << current
238
+ current = { items: [], precedence: nil }
239
+ when :semicolon
240
+ alternatives << current
241
+ return [alternatives, cursor + 1]
242
+ when :symbol, :literal
243
+ current.fetch(:items) << render_symbol(token)
244
+ when :action
245
+ current.fetch(:items) << render_action(token)
246
+ when :directive
247
+ cursor = consume_rule_directive(tokens, cursor, current)
248
+ end
249
+ cursor += 1
250
+ end
251
+ alternatives << current
252
+ [alternatives, cursor]
253
+ end
254
+
255
+ # @rbs (Array[Tokenizer::Token] tokens, Integer cursor, alternative alternative) -> Integer
256
+ def consume_rule_directive(tokens, cursor, alternative)
257
+ token = tokens.fetch(cursor)
258
+ name = token.value.delete_prefix("%")
259
+ record_directive(name, token.line, token.column, token.value)
260
+ return cursor if name == "empty"
261
+
262
+ if name == "prec"
263
+ following = tokens[cursor + 1]
264
+ if following && %i[symbol literal].include?(following.type)
265
+ alternative[:precedence] =
266
+ following.type == :literal ? following.value : terminal_name(following.value)
267
+ return cursor + 1
268
+ end
269
+ raise Ibex::Error, "#{@file}:#{token.line}:#{token.column}: %prec requires a symbol"
270
+ end
271
+
272
+ return cursor + 1 if %w[dprec merge].include?(name) && tokens[cursor + 1]
273
+
274
+ cursor
275
+ end
276
+
277
+ # @rbs (Tokenizer::Token token) -> String
278
+ def render_symbol(token)
279
+ return token.value if token.type == :literal
280
+
281
+ nonterminal_names = @nonterminal_names #: Hash[String, String]
282
+ nonterminal_names.fetch(token.value) { terminal_name(token.value) }
283
+ end
284
+
285
+ # @rbs (Tokenizer::Token token) -> String
286
+ def render_action(token)
287
+ actions = @actions #: Array[Action]
288
+ check_action_budget(actions.length + 1, token)
289
+ transformed = transform_action(token.value)
290
+ encoded = transformed.unpack1("H*").to_s
291
+ action = Action.new(
292
+ id: actions.length + 1, line: token.line, column: token.column,
293
+ original: token.value, transformed: transformed, encoded: encoded
294
+ )
295
+ actions << action
296
+ "{ #{FOREIGN_ACTION_SENTINEL}(#{encoded.inspect}) }"
297
+ end
298
+
299
+ # @rbs (String code) -> String
300
+ def transform_action(code)
301
+ transformed = code.gsub(/\$<[^>]+>\$/, "result")
302
+ transformed = transformed.gsub(/\$<[^>]+>(\d+)/) { "val[#{::Regexp.last_match(1).to_i - 1}]" }
303
+ transformed = transformed.gsub("$$", "result")
304
+ transformed = transformed.gsub(/\$(\d+)/) { "val[#{::Regexp.last_match(1).to_i - 1}]" }
305
+ transformed = transformed.gsub(/@<[^>]+>(\d+)/, '@\1')
306
+ transformed.gsub(/@\$/, "result_loc")
307
+ end
308
+
309
+ # @rbs (Array[rule] rules) -> String
310
+ def render_source(rules)
311
+ directives = @directives #: Array[Directive]
312
+ token_entries = @token_entries #: Array[[String, String?]]
313
+ starts = @starts #: Array[String]
314
+ lines = [
315
+ "# Imported from #{@file} for analysis only.",
316
+ "# C actions are opaque; Ruby parser generation is intentionally refused.",
317
+ "# #{STRUCTURAL_STATUS_MARKER}: #{structural_status}"
318
+ ]
319
+ directives.select { |directive| directive.status == :unsupported }.each do |directive|
320
+ lines << "# unsupported %#{directive.name} at #{directive.line}:#{directive.column}"
321
+ end
322
+ lines.push("class #{resolved_class_name}", "pragma extended")
323
+ token_entries.uniq.sort.each do |name, alias_name|
324
+ rendered = "token #{terminal_name(name)}"
325
+ rendered = "#{rendered} #{alias_name}" if alias_name
326
+ lines << rendered
327
+ end
328
+ render_precedence(lines)
329
+ lines << "expect #{@expected_sr}" if @expected_sr
330
+ lines << "%expect-rr #{@expected_rr}" if @expected_rr
331
+ lines << "start #{starts.uniq.map { |name| nonterminal_name(name) }.join(' ')}" unless starts.empty?
332
+ lines << "rule"
333
+ rules.each { |rule| render_rule(lines, rule) }
334
+ lines << "end"
335
+ "#{lines.join("\n")}\n"
336
+ end
337
+
338
+ # @rbs () -> String
339
+ def structural_status
340
+ directives = @directives #: Array[Directive]
341
+ unsupported = directives.select do |directive|
342
+ directive.status == :unsupported &&
343
+ !STRUCTURE_NEUTRAL_UNSUPPORTED.include?(directive.name)
344
+ end
345
+ unsupported.empty? ? "complete" : "incomplete"
346
+ end
347
+
348
+ # @rbs (Array[String] lines) -> void
349
+ def render_precedence(lines)
350
+ precedence_levels = @precedence_levels #: Array[[String, Array[String]]]
351
+ return if precedence_levels.empty?
352
+
353
+ lines << "preclow"
354
+ precedence_levels.each do |association, symbols|
355
+ rendered = symbols.map { |symbol| literal_atom?(symbol) ? symbol : terminal_name(symbol) }
356
+ lines << " #{association} #{rendered.join(' ')}"
357
+ end
358
+ lines << "prechigh"
359
+ end
360
+
361
+ # @rbs (Array[String] lines, rule rule) -> void
362
+ def render_rule(lines, rule)
363
+ alternatives = rule.fetch(:alternatives)
364
+ alternatives.each_with_index do |alternative, index|
365
+ prefix = index.zero? ? "#{rule.fetch(:lhs)}:" : " |"
366
+ items = alternative.fetch(:items)
367
+ suffix = alternative[:precedence] ? " = #{alternative.fetch(:precedence)}" : ""
368
+ lines << "#{prefix} #{items.join(' ')}#{suffix}".rstrip
369
+ end
370
+ end
371
+
372
+ # @rbs (String name, Integer line, Integer column, String detail) -> void
373
+ def record_directive(name, line, column, detail)
374
+ status = DIRECTIVES.fetch(name, :unsupported)
375
+ directives = @directives #: Array[Directive]
376
+ directives << Directive.new(
377
+ name: name, status: status, line: line, column: column, detail: detail.strip
378
+ )
379
+ end
380
+
381
+ # @rbs (String detail) -> Array[String]
382
+ def declaration_atoms(detail)
383
+ source = strip_declaration_comments(detail)
384
+ source = source.gsub(/\b[A-Z][A-Z0-9_]*\([^()\n]*\)/, " ")
385
+ source.scan(
386
+ /<[^>]*>|"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[A-Za-z_$][A-Za-z0-9_$.-]*|\d+/
387
+ ).map(&:to_s)
388
+ end
389
+
390
+ # @rbs (String source) -> String
391
+ def strip_declaration_comments(source)
392
+ pattern = %r{("(?:\\.|[^"])*"|'(?:\\.|[^'])*')|/\*.*?\*/|//[^\n]*|^[ \t]*\#[^\n]*}m
393
+ source.gsub(pattern) do |match|
394
+ match.start_with?('"', "'") ? match : " "
395
+ end
396
+ end
397
+
398
+ # @rbs (String value) -> bool
399
+ def identifier_atom?(value)
400
+ value.match?(/\A[A-Za-z_$][A-Za-z0-9_$.-]*\z/)
401
+ end
402
+
403
+ # @rbs (String value) -> bool
404
+ def literal_atom?(value)
405
+ value.start_with?('"', "'")
406
+ end
407
+
408
+ # @rbs (String value) -> String
409
+ def sanitize_symbol(value)
410
+ sanitized = value.gsub(/[^A-Za-z0-9_]/, "_")
411
+ sanitized = "_#{sanitized}" if sanitized.match?(/\A\d/)
412
+ sanitized.empty? ? "_bison_symbol" : sanitized
413
+ end
414
+
415
+ # @rbs (String value) -> String
416
+ def terminal_name(value)
417
+ return "error" if value == "error"
418
+
419
+ terminal_names = @terminal_names #: Hash[String, String]
420
+ terminal_names[value] ||= begin
421
+ sanitized = sanitize_symbol(value)
422
+ base = if sanitized.match?(/\A[A-Z][A-Z0-9_]*\z/)
423
+ sanitized
424
+ else
425
+ "BISON_T_#{sanitized.upcase}"
426
+ end
427
+ used = terminal_names.values
428
+ candidate = base
429
+ suffix = 2
430
+ while used.include?(candidate)
431
+ candidate = "#{base}_#{suffix}"
432
+ suffix += 1
433
+ end
434
+ candidate
435
+ end
436
+ end
437
+
438
+ # All imported nonterminals receive a lowercase namespace. This avoids
439
+ # Ibex's terminal-by-case convention and declaration keyword collisions.
440
+ # @rbs (String value) -> String
441
+ def nonterminal_name(value)
442
+ nonterminal_names = @nonterminal_names #: Hash[String, String]
443
+ nonterminal_names[value] ||= begin
444
+ base = "bison_nt_#{sanitize_symbol(value).downcase}"
445
+ used = nonterminal_names.values
446
+ candidate = base
447
+ suffix = 2
448
+ while used.include?(candidate)
449
+ candidate = "#{base}_#{suffix}"
450
+ suffix += 1
451
+ end
452
+ candidate
453
+ end
454
+ end
455
+
456
+ # @rbs () -> String
457
+ def resolved_class_name
458
+ return sanitize_class_name(@class_name) if @class_name
459
+
460
+ stem = File.basename(@file).sub(/\.[^.]+\z/, "")
461
+ "Imported#{sanitize_class_name(stem)}Parser"
462
+ end
463
+
464
+ # @rbs (String value) -> String
465
+ def sanitize_class_name(value)
466
+ parts = value.scan(/[A-Za-z0-9]+/).map(&:to_s)
467
+ rendered = parts.map { |part| part.sub(/\A./, &:upcase) }.join
468
+ rendered = "Grammar" if rendered.empty?
469
+ rendered = "Grammar#{rendered}" if rendered.match?(/\A\d/)
470
+ rendered
471
+ end
472
+
473
+ # @rbs (String value) -> Integer?
474
+ def first_integer(value)
475
+ match = value.match(/\d+/)
476
+ match ? Integer(match[0], 10) : nil
477
+ end
478
+
479
+ # @rbs (Integer count) -> void
480
+ def check_rule_budget(count)
481
+ return if count <= @max_rules
482
+
483
+ raise BudgetExceeded.new(
484
+ result: "budget_exhausted", phase: "rules",
485
+ observed_rules: count, max_rules: @max_rules
486
+ )
487
+ end
488
+
489
+ # @rbs (Integer count, Tokenizer::Token token) -> void
490
+ def check_action_budget(count, token)
491
+ return if count <= @max_actions
492
+
493
+ raise BudgetExceeded.new(
494
+ result: "budget_exhausted", phase: "actions", line: token.line,
495
+ observed_actions: count, max_actions: @max_actions
496
+ )
497
+ end
498
+
499
+ # @rbs (Integer value, Symbol name) -> Integer
500
+ def positive_limit(value, name)
501
+ return value if value.positive?
502
+
503
+ raise ArgumentError, "#{name} must be positive"
504
+ end
505
+
506
+ # @rbs () -> Hash[Symbol, Integer]
507
+ def bounds
508
+ {
509
+ max_bytes: @max_bytes, max_tokens: @max_tokens,
510
+ max_rules: @max_rules, max_actions: @max_actions
511
+ }
512
+ end
513
+ end
514
+ # rubocop:enable Metrics/ClassLength
515
+ end
516
+ end