ibex 0.1.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 (603) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -123
  3. data/docs/architecture.md +477 -31
  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 +106 -0
  12. data/docs/cst.md +237 -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 +306 -0
  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 +26 -0
  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 +114 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +629 -18
  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 +56 -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 +45 -6
  63. data/docs/release-readiness.md +221 -0
  64. data/docs/repair-semantics.md +123 -0
  65. data/docs/runtime-abi-evolution.md +307 -0
  66. data/docs/stability.md +234 -0
  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/examples/README.md +60 -0
  75. data/examples/calculator.y +44 -0
  76. data/examples/csv.y +32 -0
  77. data/examples/ini.y +70 -0
  78. data/examples/json.y +48 -0
  79. data/examples/tiny_language.y +75 -0
  80. data/lib/ibex/analysis/digraph.rb +128 -0
  81. data/lib/ibex/analysis/sets.rb +5 -4
  82. data/lib/ibex/analysis.rb +1 -0
  83. data/lib/ibex/artifact_set.rb +63 -0
  84. data/lib/ibex/bison_import/importer.rb +516 -0
  85. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  86. data/lib/ibex/bison_import.rb +200 -0
  87. data/lib/ibex/bounded_subprocess.rb +171 -0
  88. data/lib/ibex/cli/ambiguity.rb +75 -0
  89. data/lib/ibex/cli/analysis.rb +134 -0
  90. data/lib/ibex/cli/bison_import.rb +121 -0
  91. data/lib/ibex/cli/config.rb +128 -0
  92. data/lib/ibex/cli/counterexample_options.rb +6 -2
  93. data/lib/ibex/cli/coverage.rb +173 -0
  94. data/lib/ibex/cli/debug.rb +106 -0
  95. data/lib/ibex/cli/diagnostics.rb +136 -0
  96. data/lib/ibex/cli/documentation.rb +70 -0
  97. data/lib/ibex/cli/equiv.rb +169 -0
  98. data/lib/ibex/cli/error_messages.rb +184 -0
  99. data/lib/ibex/cli/explain.rb +85 -0
  100. data/lib/ibex/cli/fix.rb +203 -0
  101. data/lib/ibex/cli/formatting.rb +387 -0
  102. data/lib/ibex/cli/fuzz.rb +200 -0
  103. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  104. data/lib/ibex/cli/generation_artifacts.rb +189 -0
  105. data/lib/ibex/cli/generation_error_messages.rb +39 -0
  106. data/lib/ibex/cli/grammar_tests.rb +140 -0
  107. data/lib/ibex/cli/ir_tools.rb +128 -0
  108. data/lib/ibex/cli/lsp.rb +33 -0
  109. data/lib/ibex/cli/outputs.rb +133 -23
  110. data/lib/ibex/cli/racc_migration.rb +87 -0
  111. data/lib/ibex/cli/reduce.rb +202 -0
  112. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  113. data/lib/ibex/cli/samples.rb +118 -0
  114. data/lib/ibex/cli/verify.rb +116 -0
  115. data/lib/ibex/cli/watch.rb +104 -0
  116. data/lib/ibex/cli.rb +825 -55
  117. data/lib/ibex/codegen/action_locations.rb +103 -0
  118. data/lib/ibex/codegen/action_method_source.rb +209 -0
  119. data/lib/ibex/codegen/action_source.rb +119 -0
  120. data/lib/ibex/codegen/ambiguity.rb +174 -0
  121. data/lib/ibex/codegen/cst_metadata.rb +171 -0
  122. data/lib/ibex/codegen/documentation.rb +170 -0
  123. data/lib/ibex/codegen/explain.rb +380 -0
  124. data/lib/ibex/codegen/generated_action_abi.rb +293 -0
  125. data/lib/ibex/codegen/html.rb +94 -10
  126. data/lib/ibex/codegen/mermaid.rb +43 -0
  127. data/lib/ibex/codegen/railroad.rb +197 -0
  128. data/lib/ibex/codegen/railroad_documentation.rb +59 -0
  129. data/lib/ibex/codegen/rbs.rb +331 -2
  130. data/lib/ibex/codegen/report.rb +67 -15
  131. data/lib/ibex/codegen/ruby.rb +305 -66
  132. data/lib/ibex/codegen/ruby_actions.rb +146 -0
  133. data/lib/ibex/codegen/ruby_ast.rb +121 -0
  134. data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
  135. data/lib/ibex/codegen/ruby_lexer.rb +83 -0
  136. data/lib/ibex/codegen/ruby_syntax.rb +96 -0
  137. data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
  138. data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
  139. data/lib/ibex/codegen/symbol_labels.rb +1 -1
  140. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  141. data/lib/ibex/configuration/explanation.rb +421 -0
  142. data/lib/ibex/configuration/inspector.rb +232 -0
  143. data/lib/ibex/configuration.rb +580 -0
  144. data/lib/ibex/coverage/collector.rb +198 -0
  145. data/lib/ibex/coverage/event_stream.rb +103 -0
  146. data/lib/ibex/coverage/report.rb +267 -0
  147. data/lib/ibex/coverage/runtime_event_validator.rb +169 -0
  148. data/lib/ibex/coverage.rb +13 -0
  149. data/lib/ibex/delta_reducer.rb +99 -0
  150. data/lib/ibex/diff.rb +140 -0
  151. data/lib/ibex/equiv/machine.rb +135 -0
  152. data/lib/ibex/equiv.rb +373 -0
  153. data/lib/ibex/error_messages/parser.rb +159 -0
  154. data/lib/ibex/error_messages/parser_v2.rb +198 -0
  155. data/lib/ibex/error_messages/renderer.rb +65 -0
  156. data/lib/ibex/error_messages/sentence_search.rb +196 -0
  157. data/lib/ibex/error_messages/update.rb +169 -0
  158. data/lib/ibex/error_messages.rb +165 -0
  159. data/lib/ibex/fix.rb +557 -0
  160. data/lib/ibex/frontend/ast.rb +166 -7
  161. data/lib/ibex/frontend/bootstrap_parser.rb +40 -4
  162. data/lib/ibex/frontend/diagnostic.rb +81 -0
  163. data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
  164. data/lib/ibex/frontend/dsl.rb +66 -7
  165. data/lib/ibex/frontend/formatter.rb +453 -0
  166. data/lib/ibex/frontend/generated_parser.rb +644 -190
  167. data/lib/ibex/frontend/generated_parser_base.rb +170 -32
  168. data/lib/ibex/frontend/generated_parser_includes.rb +51 -0
  169. data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
  170. data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
  171. data/lib/ibex/frontend/generation.rb +33 -0
  172. data/lib/ibex/frontend/lexer.rb +162 -10
  173. data/lib/ibex/frontend/lexer_recovery.rb +84 -0
  174. data/lib/ibex/frontend/parser/declarations.rb +262 -11
  175. data/lib/ibex/frontend/parser/parameters.rb +82 -0
  176. data/lib/ibex/frontend/parser/rules.rb +43 -6
  177. data/lib/ibex/frontend/parser.rb +182 -4
  178. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  179. data/lib/ibex/frontend/regenerator.rb +27 -1
  180. data/lib/ibex/frontend/resolution.rb +70 -0
  181. data/lib/ibex/frontend/resolver.rb +217 -0
  182. data/lib/ibex/frontend/rule_documentation.rb +104 -0
  183. data/lib/ibex/frontend/source_cursor.rb +133 -9
  184. data/lib/ibex/frontend/source_document.rb +229 -0
  185. data/lib/ibex/frontend/source_loader.rb +150 -0
  186. data/lib/ibex/frontend/source_span.rb +89 -0
  187. data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
  188. data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
  189. data/lib/ibex/frontend/token_adapter/declaration_state.rb +244 -22
  190. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
  191. data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
  192. data/lib/ibex/frontend/token_adapter.rb +18 -3
  193. data/lib/ibex/frontend.rb +14 -2
  194. data/lib/ibex/fuzz.rb +219 -0
  195. data/lib/ibex/generation_input.rb +57 -0
  196. data/lib/ibex/generation_manifest.rb +211 -0
  197. data/lib/ibex/generation_transaction.rb +261 -0
  198. data/lib/ibex/generation_transaction_recovery.rb +109 -0
  199. data/lib/ibex/generation_transaction_validation.rb +196 -0
  200. data/lib/ibex/grammar_tests.rb +206 -0
  201. data/lib/ibex/ir/automaton_ir.rb +86 -11
  202. data/lib/ibex/ir/grammar_ir.rb +170 -26
  203. data/lib/ibex/ir/lexer_ir.rb +76 -0
  204. data/lib/ibex/ir/parser_contract.rb +135 -0
  205. data/lib/ibex/ir/serialize.rb +268 -50
  206. data/lib/ibex/ir/validator/automaton.rb +375 -0
  207. data/lib/ibex/ir/validator/base.rb +147 -0
  208. data/lib/ibex/ir/validator/grammar.rb +634 -0
  209. data/lib/ibex/ir/validator/lexer.rb +113 -0
  210. data/lib/ibex/ir/validator.rb +63 -0
  211. data/lib/ibex/ir.rb +71 -5
  212. data/lib/ibex/lalr/build_metrics.rb +71 -0
  213. data/lib/ibex/lalr/builder.rb +514 -52
  214. data/lib/ibex/lalr/conflict.rb +1 -0
  215. data/lib/ibex/lalr/conflict_search.rb +26 -9
  216. data/lib/ibex/lalr/counterexample.rb +38 -18
  217. data/lib/ibex/lalr/direct_lookaheads.rb +289 -0
  218. data/lib/ibex/lalr/goto_follows.rb +229 -0
  219. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  220. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  221. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  222. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  223. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  224. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  225. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  226. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  227. data/lib/ibex/lalr/ielr_partition.rb +166 -0
  228. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  229. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  230. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  231. data/lib/ibex/lalr/on_error_reductions.rb +74 -0
  232. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  233. data/lib/ibex/lalr.rb +59 -1
  234. data/lib/ibex/location.rb +129 -0
  235. data/lib/ibex/lsp/document_handlers.rb +68 -0
  236. data/lib/ibex/lsp/document_store.rb +264 -0
  237. data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
  238. data/lib/ibex/lsp/document_store_validation.rb +61 -0
  239. data/lib/ibex/lsp/initialization_handlers.rb +80 -0
  240. data/lib/ibex/lsp/navigation_handlers.rb +70 -0
  241. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  242. data/lib/ibex/lsp/position_codec.rb +113 -0
  243. data/lib/ibex/lsp/protocol_error.rb +33 -0
  244. data/lib/ibex/lsp/request_handlers.rb +46 -0
  245. data/lib/ibex/lsp/request_support.rb +94 -0
  246. data/lib/ibex/lsp/server.rb +148 -0
  247. data/lib/ibex/lsp/symbol_index.rb +241 -0
  248. data/lib/ibex/lsp/symbol_index_builder.rb +277 -0
  249. data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
  250. data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
  251. data/lib/ibex/lsp/symbol_occurrence.rb +23 -0
  252. data/lib/ibex/lsp/transport.rb +128 -0
  253. data/lib/ibex/lsp/workspace.rb +128 -0
  254. data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
  255. data/lib/ibex/lsp.rb +33 -0
  256. data/lib/ibex/messages/en.yml +28 -0
  257. data/lib/ibex/messages/ja.yml +28 -0
  258. data/lib/ibex/messages.rb +68 -0
  259. data/lib/ibex/metrics.rb +175 -0
  260. data/lib/ibex/normalize/declarations.rb +142 -7
  261. data/lib/ibex/normalize/diagnostics.rb +94 -24
  262. data/lib/ibex/normalize/expander.rb +64 -59
  263. data/lib/ibex/normalize/expression.rb +63 -40
  264. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  265. data/lib/ibex/normalize/inline_expansion.rb +465 -0
  266. data/lib/ibex/normalize/inline_validation.rb +174 -0
  267. data/lib/ibex/normalize/lexer.rb +131 -0
  268. data/lib/ibex/normalize/named_references.rb +60 -0
  269. data/lib/ibex/normalize/nodes.rb +46 -0
  270. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +70 -0
  271. data/lib/ibex/normalize/parameter_lowering.rb +143 -0
  272. data/lib/ibex/normalize/parameter_substitution.rb +125 -0
  273. data/lib/ibex/normalize/parameter_validation.rb +140 -0
  274. data/lib/ibex/normalize/parameters.rb +226 -0
  275. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  276. data/lib/ibex/normalize/recovery_declarations.rb +84 -0
  277. data/lib/ibex/normalize.rb +172 -17
  278. data/lib/ibex/racc_migration/checker.rb +122 -0
  279. data/lib/ibex/racc_migration/harness.rb +177 -0
  280. data/lib/ibex/racc_migration/report.rb +97 -0
  281. data/lib/ibex/racc_migration.rb +12 -0
  282. data/lib/ibex/rake_task.rb +116 -0
  283. data/lib/ibex/samples.rb +216 -0
  284. data/lib/ibex/table_artifact/builder.rb +266 -0
  285. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  286. data/lib/ibex/table_artifact/document.rb +39 -0
  287. data/lib/ibex/table_artifact/executor.rb +187 -0
  288. data/lib/ibex/table_artifact/serializer.rb +57 -0
  289. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  290. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  291. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  292. data/lib/ibex/table_artifact/validator.rb +253 -0
  293. data/lib/ibex/table_artifact.rb +88 -0
  294. data/lib/ibex/table_simulation/result.rb +55 -0
  295. data/lib/ibex/table_simulation/simulator.rb +253 -0
  296. data/lib/ibex/table_simulation/step.rb +62 -0
  297. data/lib/ibex/table_simulation/text.rb +31 -0
  298. data/lib/ibex/table_simulation.rb +13 -0
  299. data/lib/ibex/tables.rb +22 -69
  300. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  301. data/lib/ibex/verification_report/builder.rb +159 -0
  302. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  303. data/lib/ibex/verification_report/logical_path.rb +68 -0
  304. data/lib/ibex/verification_report/validator.rb +329 -0
  305. data/lib/ibex/verification_report.rb +69 -0
  306. data/lib/ibex/verify/action_correspondence.rb +138 -0
  307. data/lib/ibex/verify/language_witness.rb +295 -0
  308. data/lib/ibex/verify/reference_collection.rb +190 -0
  309. data/lib/ibex/verify/result.rb +73 -0
  310. data/lib/ibex/verify/verifier.rb +598 -0
  311. data/lib/ibex/verify.rb +15 -0
  312. data/lib/ibex/version.rb +1 -1
  313. data/lib/ibex/watch/runner.rb +181 -0
  314. data/lib/ibex/watch/source_snapshot.rb +98 -0
  315. data/lib/ibex/watch.rb +11 -0
  316. data/lib/ibex.rb +35 -1
  317. data/schema/automaton-ir-definitions.schema.json +401 -0
  318. data/schema/automaton-ir.schema.json +65 -0
  319. data/schema/benchmark-v1.schema.json +212 -0
  320. data/schema/benchmark-v2.schema.json +61 -0
  321. data/schema/bison-import-v1.schema.json +106 -0
  322. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  323. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  324. data/schema/construction-profile-v1.schema.json +779 -0
  325. data/schema/cst-v1.json +170 -0
  326. data/schema/diff-v1.schema.json +41 -0
  327. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  328. data/schema/equiv-v1.schema.json +64 -0
  329. data/schema/error-ux-review-v1.schema.json +609 -0
  330. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  331. data/schema/error-ux-round2-v1.schema.json +519 -0
  332. data/schema/error-ux-v1.schema.json +258 -0
  333. data/schema/explain-v1.schema.json +495 -0
  334. data/schema/fix-v1.schema.json +64 -0
  335. data/schema/fix-v2.schema.json +66 -0
  336. data/schema/fix-v3.schema.json +101 -0
  337. data/schema/frontend-diagnostics-v1.schema.json +154 -0
  338. data/schema/fuzz-regression-v1.schema.json +79 -0
  339. data/schema/fuzz-v1.schema.json +114 -0
  340. data/schema/generation-manifest-v1.schema.json +115 -0
  341. data/schema/grammar-ir-extensions.schema.json +734 -0
  342. data/schema/grammar-ir-foundation.schema.json +426 -0
  343. data/schema/grammar-ir.schema.json +271 -0
  344. data/schema/ielr-benchmark-v1.schema.json +85 -0
  345. data/schema/lexer-ir-v1.schema.json +215 -0
  346. data/schema/lexer-profile-v1.schema.json +491 -0
  347. data/schema/metrics-v1.schema.json +60 -0
  348. data/schema/migration-check-v1.schema.json +60 -0
  349. data/schema/performance-comparison-v1.schema.json +395 -0
  350. data/schema/public-performance-comparison-v1.schema.json +506 -0
  351. data/schema/public-performance-profile-v1.schema.json +360 -0
  352. data/schema/reduce-v1.schema.json +22 -0
  353. data/schema/reduce-v2.schema.json +63 -0
  354. data/schema/runtime-coverage-v1.schema.json +86 -0
  355. data/schema/runtime-event-v1.schema.json +308 -0
  356. data/schema/table-artifact-v1.schema.json +377 -0
  357. data/schema/table-simulation-v1.schema.json +70 -0
  358. data/schema/verification-report-v1.schema.json +222 -0
  359. data/schema/verify-v1.schema.json +42 -0
  360. data/sig/ibex/analysis/digraph.rbs +19 -0
  361. data/sig/ibex/artifact_set.rbs +37 -0
  362. data/sig/ibex/bison_import/importer.rbs +140 -0
  363. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  364. data/sig/ibex/bison_import.rbs +95 -0
  365. data/sig/ibex/bounded_subprocess.rbs +76 -0
  366. data/sig/ibex/cli/ambiguity.rbs +28 -0
  367. data/sig/ibex/cli/analysis.rbs +43 -0
  368. data/sig/ibex/cli/bison_import.rbs +32 -0
  369. data/sig/ibex/cli/config.rbs +35 -0
  370. data/sig/ibex/cli/counterexample_options.rbs +2 -0
  371. data/sig/ibex/cli/coverage.rbs +53 -0
  372. data/sig/ibex/cli/debug.rbs +28 -0
  373. data/sig/ibex/cli/diagnostics.rbs +51 -0
  374. data/sig/ibex/cli/documentation.rbs +29 -0
  375. data/sig/ibex/cli/equiv.rbs +49 -0
  376. data/sig/ibex/cli/error_messages.rbs +63 -0
  377. data/sig/ibex/cli/explain.rbs +31 -0
  378. data/sig/ibex/cli/fix.rbs +53 -0
  379. data/sig/ibex/cli/formatting.rbs +107 -0
  380. data/sig/ibex/cli/fuzz.rbs +47 -0
  381. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  382. data/sig/ibex/cli/generation_artifacts.rbs +69 -0
  383. data/sig/ibex/cli/generation_error_messages.rbs +19 -0
  384. data/sig/ibex/cli/grammar_tests.rbs +48 -0
  385. data/sig/ibex/cli/ir_tools.rbs +39 -0
  386. data/sig/ibex/cli/lsp.rbs +14 -0
  387. data/sig/ibex/cli/outputs.rbs +26 -3
  388. data/sig/ibex/cli/racc_migration.rbs +30 -0
  389. data/sig/ibex/cli/reduce.rbs +60 -0
  390. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  391. data/sig/ibex/cli/samples.rbs +38 -0
  392. data/sig/ibex/cli/verify.rbs +28 -0
  393. data/sig/ibex/cli/watch.rbs +43 -0
  394. data/sig/ibex/cli.rbs +182 -9
  395. data/sig/ibex/codegen/action_locations.rbs +45 -0
  396. data/sig/ibex/codegen/action_method_source.rbs +65 -0
  397. data/sig/ibex/codegen/action_source.rbs +50 -0
  398. data/sig/ibex/codegen/ambiguity.rbs +60 -0
  399. data/sig/ibex/codegen/cst_metadata.rbs +59 -0
  400. data/sig/ibex/codegen/documentation.rbs +50 -0
  401. data/sig/ibex/codegen/explain.rbs +105 -0
  402. data/sig/ibex/codegen/generated_action_abi.rbs +105 -0
  403. data/sig/ibex/codegen/html.rbs +18 -2
  404. data/sig/ibex/codegen/mermaid.rbs +16 -0
  405. data/sig/ibex/codegen/railroad.rbs +82 -0
  406. data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
  407. data/sig/ibex/codegen/rbs.rbs +86 -4
  408. data/sig/ibex/codegen/report.rbs +16 -8
  409. data/sig/ibex/codegen/ruby.rbs +105 -28
  410. data/sig/ibex/codegen/ruby_actions.rbs +54 -0
  411. data/sig/ibex/codegen/ruby_ast.rbs +34 -0
  412. data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
  413. data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
  414. data/sig/ibex/codegen/ruby_syntax.rbs +27 -0
  415. data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
  416. data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
  417. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  418. data/sig/ibex/configuration/explanation.rbs +195 -0
  419. data/sig/ibex/configuration/inspector.rbs +53 -0
  420. data/sig/ibex/configuration.rbs +242 -0
  421. data/sig/ibex/coverage/collector.rbs +78 -0
  422. data/sig/ibex/coverage/event_stream.rbs +44 -0
  423. data/sig/ibex/coverage/report.rbs +102 -0
  424. data/sig/ibex/coverage/runtime_event_validator.rbs +70 -0
  425. data/sig/ibex/coverage.rbs +7 -0
  426. data/sig/ibex/delta_reducer.rbs +38 -0
  427. data/sig/ibex/diff.rbs +47 -0
  428. data/sig/ibex/equiv/machine.rbs +54 -0
  429. data/sig/ibex/equiv.rbs +129 -0
  430. data/sig/ibex/error_messages/parser.rbs +58 -0
  431. data/sig/ibex/error_messages/parser_v2.rbs +67 -0
  432. data/sig/ibex/error_messages/renderer.rbs +23 -0
  433. data/sig/ibex/error_messages/sentence_search.rbs +80 -0
  434. data/sig/ibex/error_messages/update.rbs +43 -0
  435. data/sig/ibex/error_messages.rbs +85 -0
  436. data/sig/ibex/fix.rbs +138 -0
  437. data/sig/ibex/frontend/ast.rbs +237 -22
  438. data/sig/ibex/frontend/bootstrap_parser.rbs +13 -0
  439. data/sig/ibex/frontend/diagnostic.rbs +53 -0
  440. data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
  441. data/sig/ibex/frontend/dsl.rbs +33 -4
  442. data/sig/ibex/frontend/formatter.rbs +143 -0
  443. data/sig/ibex/frontend/generated_parser.rbs +228 -68
  444. data/sig/ibex/frontend/generated_parser_base.rbs +65 -12
  445. data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
  446. data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
  447. data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
  448. data/sig/ibex/frontend/generation.rbs +6 -0
  449. data/sig/ibex/frontend/lexer.rbs +49 -2
  450. data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
  451. data/sig/ibex/frontend/parser/declarations.rbs +57 -0
  452. data/sig/ibex/frontend/parser/parameters.rbs +28 -0
  453. data/sig/ibex/frontend/parser/rules.rbs +3 -0
  454. data/sig/ibex/frontend/parser.rbs +66 -0
  455. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  456. data/sig/ibex/frontend/regenerator.rbs +11 -0
  457. data/sig/ibex/frontend/resolution.rbs +34 -0
  458. data/sig/ibex/frontend/resolver.rbs +91 -0
  459. data/sig/ibex/frontend/rule_documentation.rbs +42 -0
  460. data/sig/ibex/frontend/source_cursor.rbs +38 -5
  461. data/sig/ibex/frontend/source_document.rbs +119 -0
  462. data/sig/ibex/frontend/source_loader.rbs +66 -0
  463. data/sig/ibex/frontend/source_span.rbs +56 -0
  464. data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
  465. data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
  466. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +83 -3
  467. data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
  468. data/sig/ibex/frontend/token_adapter.rbs +11 -2
  469. data/sig/ibex/frontend.rbs +1 -1
  470. data/sig/ibex/fuzz.rbs +71 -0
  471. data/sig/ibex/generation_input.rbs +37 -0
  472. data/sig/ibex/generation_manifest.rbs +70 -0
  473. data/sig/ibex/generation_transaction.rbs +82 -0
  474. data/sig/ibex/generation_transaction_recovery.rbs +36 -0
  475. data/sig/ibex/generation_transaction_validation.rbs +65 -0
  476. data/sig/ibex/grammar_tests.rbs +93 -0
  477. data/sig/ibex/ir/automaton_ir.rbs +35 -10
  478. data/sig/ibex/ir/grammar_ir.rbs +96 -22
  479. data/sig/ibex/ir/lexer_ir.rbs +57 -0
  480. data/sig/ibex/ir/parser_contract.rbs +69 -0
  481. data/sig/ibex/ir/serialize.rbs +75 -23
  482. data/sig/ibex/ir/validator/automaton.rbs +112 -0
  483. data/sig/ibex/ir/validator/base.rbs +69 -0
  484. data/sig/ibex/ir/validator/grammar.rbs +195 -0
  485. data/sig/ibex/ir/validator/lexer.rbs +37 -0
  486. data/sig/ibex/ir/validator.rbs +16 -0
  487. data/sig/ibex/ir.rbs +45 -5
  488. data/sig/ibex/lalr/build_metrics.rbs +60 -0
  489. data/sig/ibex/lalr/builder.rbs +150 -19
  490. data/sig/ibex/lalr/conflict_search.rbs +12 -5
  491. data/sig/ibex/lalr/counterexample.rbs +20 -14
  492. data/sig/ibex/lalr/direct_lookaheads.rbs +93 -0
  493. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  494. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  495. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  496. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  497. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  498. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  499. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  500. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  501. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  502. data/sig/ibex/lalr/ielr_partition.rbs +71 -0
  503. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  504. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  505. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  506. data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
  507. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  508. data/sig/ibex/lalr.rbs +17 -1
  509. data/sig/ibex/location.rbs +67 -0
  510. data/sig/ibex/lsp/document_handlers.rbs +24 -0
  511. data/sig/ibex/lsp/document_store.rbs +94 -0
  512. data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
  513. data/sig/ibex/lsp/document_store_validation.rbs +26 -0
  514. data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
  515. data/sig/ibex/lsp/navigation_handlers.rbs +33 -0
  516. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  517. data/sig/ibex/lsp/position_codec.rbs +44 -0
  518. data/sig/ibex/lsp/protocol_error.rbs +32 -0
  519. data/sig/ibex/lsp/request_handlers.rbs +26 -0
  520. data/sig/ibex/lsp/request_support.rbs +40 -0
  521. data/sig/ibex/lsp/server.rbs +55 -0
  522. data/sig/ibex/lsp/symbol_index.rbs +77 -0
  523. data/sig/ibex/lsp/symbol_index_builder.rbs +88 -0
  524. data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
  525. data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
  526. data/sig/ibex/lsp/symbol_occurrence.rbs +31 -0
  527. data/sig/ibex/lsp/transport.rbs +47 -0
  528. data/sig/ibex/lsp/workspace.rbs +42 -0
  529. data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
  530. data/sig/ibex/lsp.rbs +7 -0
  531. data/sig/ibex/messages.rbs +26 -0
  532. data/sig/ibex/metrics.rbs +50 -0
  533. data/sig/ibex/normalize/declarations.rbs +31 -0
  534. data/sig/ibex/normalize/diagnostics.rbs +13 -4
  535. data/sig/ibex/normalize/expander.rbs +22 -16
  536. data/sig/ibex/normalize/expression.rbs +11 -9
  537. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  538. data/sig/ibex/normalize/inline_expansion.rbs +129 -0
  539. data/sig/ibex/normalize/inline_validation.rbs +40 -0
  540. data/sig/ibex/normalize/lexer.rbs +37 -0
  541. data/sig/ibex/normalize/named_references.rbs +20 -0
  542. data/sig/ibex/normalize/nodes.rbs +14 -0
  543. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +33 -0
  544. data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
  545. data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
  546. data/sig/ibex/normalize/parameter_validation.rbs +44 -0
  547. data/sig/ibex/normalize/parameters.rbs +51 -0
  548. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  549. data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
  550. data/sig/ibex/normalize.rbs +129 -18
  551. data/sig/ibex/racc_migration/checker.rbs +36 -0
  552. data/sig/ibex/racc_migration/harness.rbs +16 -0
  553. data/sig/ibex/racc_migration/report.rbs +55 -0
  554. data/sig/ibex/racc_migration.rbs +8 -0
  555. data/sig/ibex/rake_task.rbs +51 -0
  556. data/sig/ibex/samples.rbs +60 -0
  557. data/sig/ibex/table_artifact/builder.rbs +93 -0
  558. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  559. data/sig/ibex/table_artifact/document.rbs +27 -0
  560. data/sig/ibex/table_artifact/executor.rbs +66 -0
  561. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  562. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  563. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  564. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  565. data/sig/ibex/table_artifact/validator.rbs +66 -0
  566. data/sig/ibex/table_artifact.rbs +32 -0
  567. data/sig/ibex/table_simulation/result.rbs +36 -0
  568. data/sig/ibex/table_simulation/simulator.rbs +98 -0
  569. data/sig/ibex/table_simulation/step.rbs +42 -0
  570. data/sig/ibex/table_simulation/text.rbs +14 -0
  571. data/sig/ibex/table_simulation.rbs +7 -0
  572. data/sig/ibex/tables.rbs +10 -35
  573. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  574. data/sig/ibex/verification_report/builder.rbs +53 -0
  575. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  576. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  577. data/sig/ibex/verification_report/validator.rbs +114 -0
  578. data/sig/ibex/verification_report.rbs +44 -0
  579. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  580. data/sig/ibex/verify/language_witness.rbs +112 -0
  581. data/sig/ibex/verify/reference_collection.rbs +63 -0
  582. data/sig/ibex/verify/result.rbs +52 -0
  583. data/sig/ibex/verify/verifier.rbs +170 -0
  584. data/sig/ibex/verify.rbs +6 -0
  585. data/sig/ibex/watch/runner.rbs +58 -0
  586. data/sig/ibex/watch/source_snapshot.rbs +46 -0
  587. data/sig/ibex/watch.rbs +7 -0
  588. data/sig/ibex.rbs +2 -0
  589. metadata +524 -19
  590. data/.rubocop.yml +0 -43
  591. data/CHANGELOG.md +0 -30
  592. data/Rakefile +0 -25
  593. data/Steepfile +0 -10
  594. data/docs/compat-notes.md +0 -37
  595. data/docs/lexer-coverage.md +0 -14
  596. data/docs/phase10-extensions.md +0 -27
  597. data/gemfiles/Gemfile +0 -7
  598. data/gemfiles/Gemfile.lock +0 -98
  599. data/lib/ibex/frontend/grammar.y +0 -156
  600. data/lib/ibex/runtime/parser.rb +0 -360
  601. data/lib/ibex/runtime.rb +0 -8
  602. data/sig/ibex/runtime/parser.rbs +0 -167
  603. data/sig/ibex/runtime.rbs +0 -6
@@ -1,6 +1,13 @@
1
+ ---
2
+ title: Grammar reference
3
+ description: Write compatible and extended Ibex grammars and understand their generated contracts.
4
+ ---
5
+
1
6
  # Grammar reference
2
7
 
3
- Ibex's default `racc` mode accepts the compatible grammar described here. `--mode=extended` or an explicit grammar-file
8
+ <!-- stable:compatible-parser:v2 -->
9
+
10
+ Ibex's `default` mode accepts the compatible grammar described here. `--mode=extended` or an explicit grammar-file
4
11
  `pragma extended` adds the marked syntax; extensions are never inferred from a production.
5
12
 
6
13
  ## File structure
@@ -8,6 +15,7 @@ Ibex's default `racc` mode accepts the compatible grammar described here. `--mod
8
15
  ```text
9
16
  class Namespace::Parser < OptionalSuperclass
10
17
  pragma extended # optional; must precede ordinary declarations
18
+ pragma cst # optional automatic concrete-tree mode
11
19
  declarations
12
20
  rule
13
21
  productions
@@ -23,23 +31,262 @@ Ruby copied after the parser class
23
31
  The superclass defaults to `Ibex::Runtime::Parser`. Repeated user-code blocks retain their source order and are concatenated.
24
32
  Grammar comments use `#` through end of line or `/* ... */`.
25
33
 
34
+ Extended roots may import explicit fragment files:
35
+
36
+ ```text
37
+ fragment
38
+ token SHARED
39
+ import "nested/expressions.y"
40
+ rule
41
+ shared_rule: SHARED
42
+ end
43
+ ```
44
+
45
+ Fragments own no class, superclass, pragma, options, expected-conflict count, start symbol, or user-code blocks. Their `rule`
46
+ section may be empty. Token, precedence, conversion, display, and type declarations are merged with their original locations.
47
+ `import "relative/path.y"` appears in the declaration section of a root or fragment and requires extended mode. The older
48
+ `include` spelling remains a compatible alias. Paths are
49
+ resolved relative to the including file, must be double-quoted and relative, cannot contain parent traversal, globs, or NUL, and
50
+ must resolve through symlinks to a regular file below the root grammar's canonical directory.
51
+
52
+ Resolution is deterministic depth-first order at include sites. A canonical file reached twice through a diamond is merged only
53
+ at its first occurrence; a cycle reports its exact canonical path loop. `Frontend::Resolver.new(path, mode: :extended).resolve`
54
+ returns the merged root and canonical dependency closure. `Parser#parse_fragment` parses fragment text without I/O, while normal
55
+ `Parser#parse` continues to return only a root and rejects fragment input. The exposed resolution is recursively immutable,
56
+ including AST strings and locations and defensively copied include provenance. Canonical directory ancestry, rather than a
57
+ string-prefix comparison, enforces the source-root boundary and remains valid when the source root is a filesystem or drive root.
58
+
59
+ Immediately preceding `##` line comments attach documentation to a rule:
60
+
61
+ ```text
62
+ ## First line
63
+ ## one extra leading space is retained
64
+ value: TOKEN
65
+ ```
66
+
67
+ Indentation before the comment is ignored. Ibex removes `##` and one optional space, so the example stores
68
+ `"First line\n one extra leading space is retained"`. The lines must be consecutive and directly above the LHS. Blank lines,
69
+ ordinary `#` comments, block comments, grammar tokens, and opaque actions or heredocs break attachment. The association uses
70
+ lossless segment spans rather than rescanning raw text. Documentation works in roots and fragments and is nullable on
71
+ `AST::Rule`.
72
+
73
+ For repeated definitions, every normalized user production keeps its definition's documentation. The nonterminal symbol uses
74
+ the first nonnil text; a different later nonnil text is a positioned error, while the same text is accepted. The current
75
+ Grammar IR serializes `doc` on symbols and productions.
76
+
77
+ `ibex doc [--format=markdown|html|railroad] [-o FILE] [--mode=MODE] grammar.y` renders the canonical resolved grammar. Output
78
+ defaults to Markdown on stdout. HTML is accessible and self-contained, railroad output includes visible wrapped descriptions,
79
+ and all formats escape grammar-controlled text. `-o` writes atomically and cannot alias the input grammar.
80
+
81
+ The programmatic frontend can preserve this file exactly with
82
+ `Ibex::Frontend::Parser.new(source, file: path).parse_document`. The returned source document contains the same semantic AST as
83
+ `parse` plus immutable token, whitespace, line-break, comment, action, user-code marker/body, and EOF segments. `render` reproduces the input
84
+ bytes; spans and `slice` use zero-based half-open byte offsets. Line and column positions remain one-based and count Unicode
85
+ scalar values. Input is interpreted as UTF-8 without transcoding and invalid byte sequences are rejected before lexing.
86
+ `parse_source_document` accepts either a root or, in extended mode, an explicit fragment.
87
+
88
+ `ibex fmt [--mode=default|extended] grammar.y` formats to stdout. `fmt --check FILE...` reports all invalid or noncanonical files,
89
+ and `fmt --write FILE...` validates and stages the complete batch before transactionally replacing changed files. Standard input
90
+ is available as `fmt -`; `--stdin-filename=FILE` supplies its control-byte-free diagnostic name. Check and write modes require
91
+ file paths. Formatting changes only whitespace/newline trivia, retains token, comment, action/heredoc, and user-code bytes in
92
+ order, and preserves existing mixed newline spellings at required boundaries. A new boundary uses the first newline even inside
93
+ opaque text. The output must reparse to an identical location-free AST through a stack-safe iterative comparison and is
94
+ idempotent. Write mode rejects aliased targets, preserves full modes and symlinks, and rolls back every target if rename or
95
+ directory synchronization fails. Same-directory hard-link backups are synchronized before installation. A backup whose target
96
+ could not be restored is retained and reported; cleanup failures after every target was committed are reported as status-0
97
+ warnings because they do not undo the committed update.
98
+
99
+ `ibex lsp [--stdio]` serves the same lossless frontend over LSP 3.17 Content-Length-framed standard IO. Initialization requires a
100
+ local `rootUri` or initial `workspaceFolders`; every document URI must remain within those canonical roots. The server uses
101
+ full-text synchronization and UTF-16 positions. Open buffers override disk throughout fragment resolution, so changing or
102
+ creating an included fragment re-diagnoses all known dependent roots. It supports diagnostics, definition, references,
103
+ prepare-rename, rename, and hover. Rename accepts only defined identifiers, checks scope/collisions, includes open versions in
104
+ the workspace edit, and reparses/re-resolves every affected closure before returning edits. Grammar actions, heredocs, and user
105
+ code are never executed. See [editor setup](editor-setup.md).
106
+
107
+ `parse_with_diagnostics(max_diagnostics: 20)` collects up to the limit independently from the lexical and syntax phases, merges
108
+ them in source order, and returns the globally earliest records. Recovery is deliberately limited to complete declarations,
109
+ rules, or outer alternatives at balanced delimiters, so an error inside a nested group cannot synchronize at that group's `|`.
110
+ The result may expose a partial AST containing later valid rules, but `success?` remains false and the original source document
111
+ does not adopt that AST. `ibex diagnose` renders the same records as text or versioned JSON. After a clean root parse it resolves
112
+ includes and emits the first cross-file security, missing-target, cycle, or fragment-syntax failure as
113
+ `frontend.resolution_error`; cross-file recovery is intentionally bounded to that one record. Permission and other actual
114
+ filesystem read failures remain CLI invocation errors on stderr and do not produce a JSON envelope.
115
+
116
+ ## Generated parser execution boundary
117
+
118
+ Static frontend, formatting, LSP, documentation, IR, and analysis operations
119
+ treat parser actions, generated lexer actions, and user-code sections as opaque
120
+ data and do not execute them. Every semantic parse executes parser production
121
+ actions. Generated lexer actions additionally execute for `parse(source)`,
122
+ generated-lexer `lex(source).do_parse`, and generated-lexer
123
+ `parse_with_syntax(source)`. A handwritten `next_token` used by `do_parse` or
124
+ no-argument `parse_with_syntax` does not invoke generated lexer actions, nor do
125
+ the caller-fed `yyparse` and `push` / `finish` APIs.
126
+
127
+ Loading the generated file may execute `header`, `inner`, and `footer` code on
128
+ all of these paths. Generated runtime execution is trusted application code,
129
+ not a sandbox.
130
+
131
+ Generated `parse_syntax` and `incremental_session` are syntax-only: they
132
+ suppress parser production actions but still execute generated lexer actions.
133
+ They may also inherit side effects from loading user sections. A future
134
+ nonexecuting syntax profile would require a declarative built-in-only lexer and
135
+ no user-code sections; the current generated parser API is not that profile.
136
+
26
137
  ## Declarations
27
138
 
28
- - `pragma extended` enables extended syntax for this grammar even when the CLI uses its default or explicit `--mode=racc`.
139
+ - `pragma extended` enables extended syntax for this grammar even when the CLI uses its default or explicit `--mode=default`.
29
140
  It must immediately follow the class header, before every ordinary declaration. Unknown, duplicate, and misplaced pragmas
30
- are positioned errors. The pragma is consumed by the frontend and is not stored in AST or Grammar IR output.
31
-
141
+ are positioned errors. The frontend records the effective mode on the root AST, and the current Grammar IR records extended mode
142
+ additively so downstream generators preserve its runtime behavior.
143
+ - `pragma cst` enables extended syntax and builds a pure-syntax Red/Green tree
144
+ in parallel with the ordinary semantic value stack. Distinct pragmas may be
145
+ combined in the class header; repeating either one is an error. Grammar IR
146
+ The current Grammar IR stores the optional `cst: true` setting.
147
+ - `import "relative/path.y"` inserts one explicit fragment through the canonical resolver. `include` is an accepted
148
+ compatibility spelling. Imports are available only in extended mode. Parsing source text alone performs no filesystem
149
+ access; path-based callers use `Frontend::Resolver` to resolve the import graph.
150
+ - `## text` is a lossless line comment rather than a parser declaration. A consecutive block immediately above a rule supplies
151
+ its documentation as described above.
32
152
  - `token NAME ...` declares terminals for typo diagnostics. It is optional. Uppercase names and quoted strings are terminals;
33
- lowercase names are nonterminals unless they are `error`.
153
+ lowercase names are nonterminals unless they are `error`. In extended mode, `token PLUS "+"` declares `PLUS` and assigns
154
+ `"+"` as its display name; the dedicated `display` declaration remains available.
34
155
  - A `prechigh ... preclow` block lists precedence from high to low; `preclow ... prechigh` lists it from low to high. Each level
35
- begins with `left`, `right`, or `nonassoc` followed by one or more terminals.
156
+ begins with `left`, `right`, or `nonassoc` followed by one or more terminals. Extended `%precedence` assigns a level without
157
+ associativity; an equal-level shift/reduce choice therefore remains an unresolved, counted default shift.
36
158
  - `options no_result_var` makes an action's final expression its value. `omit_action_call` is enabled by default;
37
159
  `no_omit_action_call` disables it.
38
160
  - `expect N` suppresses the warning when exactly N unresolved shift/reduce conflicts remain. Conflicts resolved by precedence are
39
161
  retained in Automaton IR but are not counted.
40
- - `start name` overrides the first rule as the start symbol.
162
+ - Extended `%expect-rr N` records the expected reduce/reduce count. Under `--warnings=error`, generation succeeds only when
163
+ both declared counts match.
164
+ - Extended roots accept one `parser ... end` block before `rule`. `algorithm`
165
+ accepts exactly `slr`, `lalr`, `ielr`, or `lr1`; `entries` accepts exactly
166
+ `shared` or `isolated`. Keys may appear in either order, but duplicate blocks,
167
+ duplicate keys, unknown keys or values, and `algorithm auto` are positioned
168
+ errors. `entries isolated` requires at least two declared start symbols.
169
+ Fragments cannot own parser-wide construction. During canonical generation a
170
+ matching `--algorithm` or `--entry-isolation` is accepted and a conflicting
171
+ request is rejected. Analysis and `ibex test` may explicitly choose another
172
+ algorithm and report the declared and selected values as noncanonical.
173
+ - `start name` overrides the first rule as the start symbol. Extended mode accepts an ordered list such as
174
+ `start program expression`. The first name remains the primary entry for `do_parse`; generated parsers also expose
175
+ `parse_program` and `parse_expression`. Shared construction attributes each conflict to its reachable entries and marks a
176
+ conflict as composite when it exists only after their LALR states are merged. `--entry-isolation` builds disjoint state sets
177
+ for every entry and can remove such composite conflicts at the cost of a larger table.
178
+ - `Parser#expected_tokens_exact` simulates reductions and gotos on a private stack to report only viable lookaheads. Extended
179
+ grammars use this LAC result for `expected_tokens`; compatible grammars keep the historical current-state result.
41
180
  - `convert ... end` changes external token objects. The second column is a quoted string containing Ruby source, not the value
42
181
  itself: `NUM ':number'` uses `:number`, while `NUM '"number"'` uses the String `"number"`.
182
+ - Extended mode accepts `display SYMBOL "human name"` to give a terminal or nonterminal a human-facing label without changing
183
+ its identity. Runtime errors, `expected_tokens`, and text, graph, and HTML reports prefer that label.
184
+ - Extended mode accepts `type SYMBOL "RBS type"` to describe the symbol's semantic value. Display labels and type spellings
185
+ must be non-empty quoted values on the declaration line. Type spellings are copied as opaque RBS and should be checked with
186
+ normal RBS validation. A type declared for an eliminated `%inline` rule is retained on its composition-plan result; a display
187
+ label for an eliminated inline rule is rejected because no runtime or diagnostic symbol remains to consume it.
188
+ - Extended roots accept one `%recover sync: TOKEN ...` declaration. Every name must be a unique declared terminal other than
189
+ `error`. When explicit yacc recovery cannot shift `error`, the runtime discards through the first listed synchronization
190
+ token it encounters, pops to a state that accepts it, and then processes that retained token normally.
191
+ - Extended roots accept repeated `%on_error_reduce NAME ...` declarations for nonterminals. Names on one line share a priority;
192
+ each later declaration has higher priority. A uniquely highest-priority completed production fills only table cells that
193
+ would otherwise be errors, so explicit shifts, reductions, accepts, and conflict decisions remain authoritative.
194
+ - Extended roots accept ordered `%test accept "source"` and `%test reject "source"` declarations. Sources must use
195
+ double-quoted Ruby literals and exact duplicate expectation/source pairs are rejected. The current Grammar IR retains their
196
+ decoded source and location; ordinary generated parser tables do not.
197
+
198
+ ## Generated lexer (extended mode)
199
+
200
+ ```text
201
+ lexer
202
+ skip /\s+/
203
+ NUMBER /\d+/ { |text| Integer(text, 10) }
204
+ state STRING do
205
+ on '"' { pop_state; emit :STRING_END }
206
+ CHUNK /[^"\\]+/
207
+ end
208
+ on '"' { push_state :STRING; emit :STRING_BEGIN }
209
+ end
210
+ ```
211
+
212
+ Each state tries its rules at the current position with an internal `\A` anchor.
213
+ The longest lexeme wins; declaration order breaks equal-length ties. A named
214
+ rule emits its declared terminal and uses the lexeme as its value. Its optional
215
+ action may return a converted value or call `emit TOKEN, value`. `skip` consumes
216
+ without emitting; `on` must call `emit` or `skip`.
217
+
218
+ `state NAME do ... end` creates an exclusive state. Lexer actions call
219
+ `push_state` and `pop_state`; parser actions use the public `lexer_state`
220
+ reader/writer when grammar context changes tokenization. `INITIAL` is reserved,
221
+ states are flat and unique, named rules must reference declared terminals, and
222
+ patterns that match the empty string are rejected.
223
+
224
+ Generated `parse(source, file: "(input)")` accepts String, IO, or Fiber input.
225
+ IO/Fiber chunks can end inside tokens. Locations use one-based grapheme
226
+ `column`/`grapheme_column`, explicit byte columns, and half-open
227
+ `start_byte`/`end_byte` offsets. Unicode property escapes use Ruby Regexp
228
+ semantics.
229
+
230
+ Regex execution remains subject to Ruby Regexp complexity. The static lint
231
+ flags common nested-quantifier shapes as `lexer_redos`; `--warnings=all,error`
232
+ promotes the warning to an error. This heuristic is not a proof of safety.
233
+ Applications must still bound untrusted input and review patterns. See the
234
+ [lexer migration guide](lexer-migration.md) and [ADR 0014](decisions/0014-versioned-generated-lexer.md).
235
+
236
+ ## Concrete syntax trees
237
+
238
+ With `pragma cst`, every shift and reduction builds immutable pure-syntax
239
+ Green values on a stack parallel to semantic values. Semantic actions execute
240
+ unchanged and never enter syntax children. `parse`, `do_parse`, and `yyparse`
241
+ return the semantic result; generated lexers additionally expose
242
+ `parse_with_syntax(source, file:)`, whose result provides `value`,
243
+ `syntax_root`, and `diagnostics`. The root is always
244
+ `source_file(start-symbol, $eof)`. Lazy `CST::SyntaxNode` and
245
+ `CST::SyntaxToken` wrappers provide parents, offsets, spans, locations,
246
+ pattern matching, and typed `@node` fields.
247
+
248
+ Generated lexers accept `--cst-trivia=leading|balanced|drop`; `attach` is an
249
+ alias for `leading`. Leading ownership places skipped text on the following
250
+ token. Balanced ownership places text through the first newline on the
251
+ preceding token and the remainder on the next token. EOF owns final trivia.
252
+ Drop omits trivia and deliberately disables coordinate and incremental APIs.
253
+
254
+ Lexical failures, yacc recovery, panic discards, and bounded repair retain
255
+ consumed input in error nodes, skipped trivia, or zero-width missing tokens.
256
+ Inspect `diagnostics`, `contains_error?`, `each_error`, and token
257
+ `missing?`/`error?`; an unrecoverable parse is rooted under
258
+ `synthetic_root`. Application exceptions still propagate. Only current
259
+ format-v6 structured CST tables are executable; older CST tables fail before
260
+ token consumption and must be regenerated. See the
261
+ [CST guide](cst.md) and [migration guide](cst-migration.md).
262
+
263
+ ## Generated AST nodes and traversal
264
+
265
+ An extended, action-free alternative may end with an explicit node shape:
266
+
267
+ ```text
268
+ rule
269
+ expression: expression PLUS expression @node Addition(left, operator, right)
270
+ | NUMBER @node Number(value)
271
+ end
272
+ ```
273
+
274
+ Fields map positionally to normalized RHS values. Their count must match, each
275
+ must be a unique non-keyword Ruby local identifier, and a node name must be a
276
+ Ruby constant identifier. Reusing a node name is allowed only with the same
277
+ ordered fields. `@node` cannot be combined with a trailing or middle semantic
278
+ action; write the explicit action instead when construction is not positional.
279
+
280
+ The generated parser defines the classes under its `AST` module. Ruby 3.2 and
281
+ later use `Data`; Ruby 3.0 and 3.1 use an immutable keyword-Struct compatibility
282
+ implementation with the same readers, `deconstruct`, and `deconstruct_keys`.
283
+ Generated RBS types each field from its grammar symbol and infers a fully
284
+ annotated nonterminal as the union of its node classes.
285
+
286
+ `AST::Visitor#visit` dispatches to `visit_<node>` and recursively visits fields
287
+ by default. `AST::Listener#walk` calls `enter_<node>`, walks fields, and calls
288
+ `exit_<node>`. The generated RBS enumerates every hook, so a consumer can
289
+ subclass either base under Steep without maintaining a parallel node list.
43
290
 
44
291
  ## Productions and actions
45
292
 
@@ -56,16 +303,110 @@ end
56
303
  Alternatives use `|`; a trailing semicolon is optional. `= TOKEN` overrides a production's precedence. The `error` terminal
57
304
  enables yacc-style recovery.
58
305
 
306
+ Extended grammars can write `%empty` as the sole RHS item to document an empty alternative. An implicit empty alternative stays
307
+ compatible but produces `implicit_empty` under extended warning analysis.
308
+
59
309
  Actions are opaque Ruby between balanced braces. `val` contains RHS values, `result` begins as `val[0]`, and `_values` is a copy
60
310
  of the surrounding value stack. With `no_result_var`, the action's evaluated value is used directly. A middle action becomes an
61
311
  empty helper production and consumes one value position in the enclosing RHS.
62
312
 
313
+ When a lexer returns `[token, value, location]`, actions can read the corresponding locations as `@1`, `@2`, and so on. `@$`
314
+ is the current reduction's immutable `Ibex::Runtime::LocationSpan`. A nonempty span covers the first through last located RHS
315
+ entry; an empty production is zero-width at the current lookahead and remains unlocated if no lookahead location was supplied.
316
+ A zero-width span's `end_*` coordinates equal its start even if the lookahead supplies wider end coordinates. A middle action
317
+ follows that empty-production rule, while its numbered locations address the visible left context. Numbered
318
+ references outside the action's value range are generation errors. Location expressions in strings, regular expressions,
319
+ symbols, comments, and heredoc bodies remain literal text, and ordinary Ruby instance variables are unchanged.
320
+
321
+ `Ibex::Location` is an immutable one-based source range with optional half-open byte offsets. Instance `join` and class
322
+ `Ibex::Location.join` return a covering range and reject mixed files. Lexer-owned hashes and objects remain valid. Inside an
323
+ action, `loc(1)` and `loc(:name)` are callable equivalents of numbered and named RHS locations, while `result_loc` returns the
324
+ current synthesized span. Calls outside an action, unknown names, and out-of-range positions fail explicitly. Parsers that use
325
+ only two-element tokens and no location-sensitive action or observer do not allocate a parallel location stack.
326
+
63
327
  Action and `inner` backtraces use the original grammar filename and line by default. `--line-convert-all` applies the same mapping
64
328
  to `header` and `footer`; `-l` keeps all backtraces on generated-file lines.
65
329
 
330
+ ### Constructor parameters (extended mode)
331
+
332
+ `%param name` adds a required keyword argument to the generated parser constructor. A quoted RBS type is optional:
333
+
334
+ ```text
335
+ %param context "ParserContext"
336
+ %param lexer
337
+ ```
338
+
339
+ `Parser.new(context: ..., lexer: ...)` stores the objects as `@context` and `@lexer`. Semantic actions receive locals with the
340
+ same names, so the context is available without global state; lexer methods in the `inner` section use the instance variables.
341
+ The generated RBS declares typed instance variables and constructor keywords, using `untyped` when no type is supplied.
342
+ Parameters are root-only, unique Ruby local identifiers, and cannot be Ruby keywords. The generated initializer is prepended,
343
+ so a custom superclass initializer may receive any remaining keyword arguments.
344
+
345
+ ### Parameterized rules (extended mode)
346
+
347
+ A parameterized rule is a structural template:
348
+
349
+ ```text
350
+ list(X): X:value { result = [value] }
351
+ | list(X) ',' X { result = val[0] + [val[2]] }
352
+ wrapped(X): (X | list(X))?
353
+ numbers: wrapped(list(NUM)):items { result = items }
354
+ ```
355
+
356
+ The ordered formals are identifiers and calls may be nested. The callee and opening parenthesis must be byte-adjacent.
357
+ Consequently, `list(NUM)` is a call while `ITEM (A | B)` retains its existing meaning as a symbol followed by an EBNF group.
358
+ A named reference or `?`, `*`, or `+` after the closing parenthesis applies to the specialized result.
359
+
360
+ Templates are not standalone nonterminals and cannot be the start symbol. Repeated definitions must use the same ordered
361
+ formals. Duplicate formals, mixed plain/template definitions, terminal collisions, undefined templates, and arity mismatches
362
+ are positioned errors. Formal occurrences are replaced structurally through nested calls, groups, suffixes, and separated
363
+ lists. `X:value` applies `value` to the substituted symbol or call. A formal `= X` precedence override requires that invocation
364
+ to pass one plain symbol for `X`; ordinary precedence overrides are retained unchanged. Named references inside arguments and
365
+ using a formal as a callee are rejected to avoid ambiguous capture.
366
+
367
+ The Normalizer memoizes a specialization before expanding its body, so direct and mutual same-argument recursion reuse one
368
+ internal `$parameter_N` nonterminal. Resumable item and EBNF continuations on an explicit depth-first worklist preserve ordinary
369
+ helper and production order while avoiding dependence on the Ruby stack. The default is
370
+ 1,000 distinct specializations; programmatic callers can configure the positive-Integer
371
+ `max_parameter_specializations:`. Argument-changing recursive instantiation is rejected by structural cycle detection rather
372
+ than an arbitrary depth boundary. Specialized productions retain template actions,
373
+ precedence, types, documentation, locations, and include chains. The current Grammar IR records
374
+ `expansion.parameter {rule, arguments}`.
375
+
376
+ ### Inline rules (extended mode)
377
+
378
+ `%inline` directly before a definition marks a reusable phrase for structural substitution:
379
+
380
+ ```text
381
+ %inline atom: NUM:value { result = value }
382
+ | '(' expression ')' { result = val[1] }
383
+ %inline pair(X): X X { result = val }
384
+ expression: pair(atom):values { result = values }
385
+ ```
386
+
387
+ The marker must be the exact `%inline` directive followed by whitespace; `%` inside Ruby actions remains Ruby. Inline
388
+ definitions work in roots and fragments and may be plain or parameterized. They do not become final Grammar IR symbols or
389
+ productions. Every definition of a name must agree on its inline marking and parameter formals. Inline rules cannot collide
390
+ with terminals or be the start symbol. Direct, mutual, or indirect cycles whose path contains an inline rule are rejected,
391
+ including cycles through ordinary rules, nested EBNF, and parameterized calls.
392
+
393
+ Alternatives are substituted left-to-right before LR construction. Nested or repeated uses form a deterministic cartesian
394
+ product. The caller's explicit precedence override wins; otherwise the rightmost precedence-contributing inline phrase retains
395
+ its explicit override, and flattened terminals retain normal rightmost-terminal precedence. `Ibex::Normalizer` accepts a
396
+ positive-Integer `max_inline_expansions:` limit, defaulting to 10,000 materialized productions. Parameter actuals contribute
397
+ cycle edges only when their callee position is transitively live, and both cycle validation and expansion use heap worklists so
398
+ grammar nesting is independent of the Ruby call stack.
399
+
400
+ Eliminated reductions still run their explicit or implicit actions in logical post-order. Named references, `val`, `_values`,
401
+ `@N`, `@$`, empty spans, middle actions, `result`/`no_result_var`, and parser instance methods retain their logical rule view.
402
+ The current Grammar IR serializes the executable sequence in `action.composition.plan` and records
403
+ `expansion.inline {rule}`; dump/load followed by code generation preserves it. `yyaccept` and `yyerror` stop the remaining
404
+ logical fragments and caller after the current fragment completes, and `yyerrok` does not erase that `yyerror`.
405
+
66
406
  The action scanner handles nested braces, quoted/backtick strings and interpolation, `%q/%Q/%w/%W/%i/%I/%x/%r/%s`, regular
67
407
  expressions, comments, character literals, and unquoted, single-quoted, double-quoted, or backtick heredocs. Indented, squiggly,
68
- interpolated, and multiple heredocs on one opener line are supported. See [lexer coverage](lexer-coverage.md).
408
+ interpolated, and multiple heredocs on one opener line are supported. Heredoc terminators follow their indentation mode, and
409
+ multiple openers on one line are consumed in source order.
69
410
 
70
411
  ## Runtime errors
71
412
 
@@ -73,11 +414,69 @@ The default `on_error(token_id, value, value_stack)` raises `Ibex::ParseError`.
73
414
  production to recover. Unknown external token objects receive a temporary negative internal id, remain printable through
74
415
  `token_to_str`, and always invoke `on_error` before recovery is attempted.
75
416
 
76
- Three optional observer methods default to no-ops. `on_shift(token_id, value, state)` follows each ordinary input-token shift;
417
+ With `%recover sync:`, a returned `on_error` first permits ordinary yacc `error`-token recovery. Only when no stack state can
418
+ shift `error` does panic-mode synchronization begin. Discarded application tokens call `on_discard` with reason `recovery`;
419
+ the selected synchronization token is not discarded. `on_error_recover` and the `recover` event fire once after a stack state
420
+ that accepts the synchronization token is found. EOF before a usable synchronization point rejects the parse.
421
+
422
+ Optional observer methods default to no-ops. `on_shift(token_id, value, state)` follows each ordinary input-token shift;
77
423
  `on_reduce(production_id, values, result)` follows a completed semantic action and goto; and
78
424
  `on_error_recover(token_id, value, value_stack)` follows a successful synthetic `error` shift while retaining the original
79
- unexpected-token context. Hook return values are ignored and exceptions propagate. See
80
- [ADR 0013](decisions/0013-runtime-observation-hooks.md) for exact ordering and snapshot semantics.
425
+ unexpected-token context. Their `on_shift_location`, `on_reduce_location`, and `on_error_recover_location` companions add
426
+ locations while preserving the original hook signatures. `on_discard(token_id, value, location, reason)` reports an
427
+ application token removed by yacc recovery. Hook return values are ignored and exceptions propagate. `trace_value_printer=`
428
+ opts a parser into value rendering in `yydebug`; without it, traces never expose semantic values. Extended grammars may define
429
+ symbol-specific `%printer SYMBOL { Ruby expression }` formatters. Their `value` local has the symbol's declared semantic type,
430
+ and declared `%param` locals are also available. A programmatic printer overrides generated symbol formatters. Neither form is
431
+ called unless `yydebug` is true, and formatter failures are rendered by exception class without inspecting the value. See
432
+ [ADR 0010](decisions/0010-committed-runtime-observation.md) for the observation boundary.
433
+
434
+ For external tooling, `observe { |event| ... }` registers an ordered observer and returns an opaque subscription accepted by
435
+ `unobserve`. Events are immutable, sequence-numbered per parse session, and cover `start`, `shift`, `reduce`, `error`, `recover`,
436
+ `discard`, `accept`, and `reject`. Semantic values and locations are bounded JSON summaries rather than live objects.
437
+ `Ibex::Runtime::EventJSONLTracer.attach(parser, io:)` writes the versioned schema at
438
+ `schema/runtime-event-v1.schema.json`; write and serialization failures propagate. See
439
+ [ADR 0010](decisions/0010-committed-runtime-observation.md).
440
+
441
+ Every parser instance owns immutable `Runtime::ResourceLimits`. The defaults allow a 10,000-entry LR state stack and 100
442
+ recovery entries per parse. Pass `resource_limits:` to the generated parser constructor or replace it while the instance is
443
+ idle. Stack shifts/gotos and recovery entries that exceed their budget raise `Ibex::ResourceLimitError` with structured
444
+ resource, limit, observed value, state, and location data. Concurrent parses share immutable generated tables but must use
445
+ distinct parser instances.
446
+
447
+ Assign an immutable `Ibex::Runtime::RepairPolicy` before parsing to opt into bounded insertion, deletion, and replacement search.
448
+ The default costs are 1/1/2 with maximum cost 3, 5,000 configurations/table actions, eight lookahead records, three successful
449
+ shifts, and a 256-state simulated stack. `on_repair(plan)` observes the selected immutable edits after the one `on_error` call and
450
+ before normal action replay. Insertions carry nil values, replacements retain the original value/location, and search failure
451
+ continues into yacc recovery without reporting the incident twice. Push parsing may return `:need_more` while retaining the
452
+ unexpected token. Semantic `yyerror` is not automatically repaired. See [ADR
453
+ 0012](decisions/0012-bounded-nonexecuting-analysis.md).
454
+ The gallery JSON [error UX evidence](error-ux.md) records the SP-4 baseline:
455
+ 8 of 10 selected plans were assessed useful, so the bounded single-plan feature
456
+ continues as an explicit experimental option.
457
+
458
+ The versioned stream can be converted to grammar-test coverage with `ibex coverage collect EVENTS.jsonl`, combined across
459
+ processes with `coverage merge`, and gated with `coverage check --min-states=PERCENT --min-productions=PERCENT`. State coverage
460
+ counts the initial state plus committed shift, reduce-goto, and recovery destinations; production coverage counts committed
461
+ reductions. Complete sessions and generated-parser metadata are required. Reports follow
462
+ `schema/runtime-coverage-v1.schema.json`; see [ADR 0010](decisions/0010-committed-runtime-observation.md).
463
+
464
+ `ibex debug AUTOMATON.json [TOKEN...]` simulates shifts, reductions, gotos, accept, and error directly from validated Automaton
465
+ IR. It never executes actions. When tokens are omitted, supply one terminal name or unique display name per stdin line; a blank
466
+ line or EOF finishes the input. Use `--format=json` for `schema/table-simulation-v1.schema.json`, and bound pathological tables
467
+ with positive `--max-steps` and `--max-stack`. See [ADR 0012](decisions/0012-bounded-nonexecuting-analysis.md).
468
+
469
+ ## Grammar-declared tests
470
+
471
+ `ibex test [--mode=MODE] [--algorithm=NAME] [--entry-isolation] [--timeout=SECONDS] grammar.y` executes every `%test` in source
472
+ order. The generated parser class must be constructible without arguments and define `parse(source)`. Each case uses a fresh
473
+ instance. A normal return counts as acceptance, `Ibex::Runtime::ParseError` counts as rejection, and lexer/application
474
+ exceptions are test errors rather than syntax rejections. Empty suites and grammars with required `%param` declarations fail
475
+ explicitly.
476
+
477
+ The complete suite runs in an isolated Ruby child process with a ten-second default timeout. Generated footer guards remain
478
+ false because a separate runner loads the parser file. Output is TAP-like and the command exits nonzero on any mismatch,
479
+ exception, timeout, or invalid child result. This is process isolation for reliable tooling, not a sandbox for untrusted code.
81
480
 
82
481
  ## Extended EBNF and names
83
482
 
@@ -93,8 +492,8 @@ Extended mode supports:
93
492
  Parenthesized groups may contain sequences, alternatives, and nested EBNF, for example `(KEY VALUE)*`, `(A | B)+`, or
94
493
  `separated_list((KEY VALUE), ',')`. A one-item group has that item's value; a multi-item group has an Array of its item values;
95
494
  an empty group has `nil`. Named references must be unique in an outer alternative and cannot use `result`, `val`, or `_values`;
96
- references inside a group are rejected because the group is lowered behind one outer value slot. Text, DOT, and HTML reports
97
- render lowered helper nonterminals as their original EBNF expressions instead of exposing generated helper names.
495
+ references inside a group are rejected because the group is lowered behind one outer value slot. Text, DOT, Mermaid, and HTML
496
+ reports render lowered helper nonterminals as their original EBNF expressions instead of exposing generated helper names.
98
497
 
99
498
  Actions and named references are supported on an outer production alternative, but not inside a parenthesized EBNF group.
100
499
  Move the action or binding to a separately named ordinary rule and reference that rule from the group.
@@ -102,9 +501,221 @@ Move the action or binding to a separately named ordinary rule and reference tha
102
501
  ## Strict diagnostics
103
502
 
104
503
  Grammar IR retains structured diagnostics for undeclared or unused terminals, unreachable nonterminals, duplicate productions,
105
- and a start symbol that cannot derive any terminal sentence. They remain silent by default for compatibility. `--warnings=all`
106
- prints them, `--warnings=all,error` or `--warnings=error` promotes them to command failures, and `--warnings=none` explicitly
107
- suppresses them.
504
+ unused precedence declarations, explicitly declared terminals used only by unreachable rules, and a start symbol that cannot
505
+ derive any terminal sentence. They remain silent by default for compatibility. `--warnings=all` prints them,
506
+ `--warnings=all,error` or `--warnings=error` promotes them to command failures, and `--warnings=none` explicitly suppresses them.
507
+ An unexpected LALR conflict also gets an advisory `--algorithm=ielr` note when IELR removes at least one unresolved
508
+ conflict; this note does not change generation or exit status.
509
+
510
+ `ibex check --ambiguity grammar.y` searches every parser conflict for a complete sentence accepted through two interpretations.
511
+ `--max-tokens` and `--max-configurations` bound the search per conflict; `--algorithm=lr1` excludes conflicts introduced only by
512
+ LALR merging. Exit status 1 means a concrete ambiguity was found, 2 means a configuration budget was exhausted, and 0 means no
513
+ ambiguity was found within the declared bounds. The last result is not a general proof of unambiguity. `--format=json` emits
514
+ the versioned check result and explored counts.
515
+
516
+ When an empty helper created for a middle action participates in a conflict, Automaton IR, text reports, HTML, and
517
+ `ibex explain` retain the action's source location as `midrule_origins`. This makes the otherwise synthetic reduction traceable
518
+ to the grammar expression that introduced it.
519
+
520
+ ## Transactional generation and watch mode
521
+
522
+ Ruby generation renders all requested files before replacing any target. Existing targets keep their modes, `--executable`
523
+ selects an executable parser mode, and generated paths that alias an input, have multiple hard links, or collide by portable
524
+ case/Unicode spelling are rejected. Companion outputs publish before the parser.
525
+
526
+ `--manifest[=FILE]` opts into a version-1 JSON manifest; without `=FILE`, `parser.rb` uses `parser.ibex.json`. The manifest is
527
+ published last and records the exact canonical root, fragments, IR input, and message bytes consumed, relevant generation
528
+ options, and every other artifact's path, size, and SHA-256 digest. `--check --manifest` compares all requested output bytes and
529
+ the manifest without rewriting them. `Ibex::GenerationManifest.validate_file(path)` validates the document and its current
530
+ artifact bytes. For coherent concurrent reads, read the manifest, verify every entry, and retry from a newly read manifest if
531
+ anything is missing or mismatched.
532
+
533
+ The version-1 `options` object is the existing open map for additive generation evidence. It now always records the effective
534
+ `cst_trivia` value: an omitted option uses the built-in `leading` default, and the compatibility spelling `attach` is recorded
535
+ canonically as `leading`. Adding this entry changes neither the manifest's required root shape nor its schema version.
536
+
537
+ `--watch` repeatedly applies the same transaction to Ruby file generation. It observes the root, the latest successful include
538
+ closure, unresolved include attempts, an optional messages file, and repairable output paths. Failed candidates leave the last
539
+ successful generation intact; an unchanged failure is reported once. Source changes during render or publication retry after
540
+ debouncing. Watch mode requires a grammar file and cannot be combined with stdin, `--from`, `--check`, or `--check-only`.
541
+ `SIGINT` and `SIGTERM` exit with status 130 and 143. Rake tasks are timestamp-based and reject `--watch`.
542
+
543
+ ## Example-keyed error messages
544
+
545
+ `ibex errors --list grammar.y` prints a deterministic `ibex-messages v2` template without writing a file. Each entry is keyed by
546
+ a shortest token sentence that reaches a syntax error, rather than by an unstable automaton state number:
547
+
548
+ ```text
549
+ # ibex-messages v2
550
+ sentence: NUM '+' ')'
551
+ ## E0042
552
+ # entry: expression
553
+ # state: 7
554
+ # expected: '(', NUM
555
+ | An operand or opening parenthesis is required before ')'.
556
+ end
557
+ ```
558
+
559
+ The `# state:` and `# expected:` lines are review hints; the sentence and error ID are the durable keys. Message lines start with
560
+ `|`; multiple lines are joined with newlines, and `\\`, `\n`, `\t`, and `\r` are the supported escapes. Blank lines and comments
561
+ are ignored.
562
+
563
+ `ibex errors --update grammar.y` atomically writes `grammar.messages`; use `--update=FILE`, `--algorithm=NAME`, or an IR `--from`
564
+ option to select another destination or automaton. It keeps IDs and message bodies, and reports three review classes on stdout:
565
+ `unreachable` when a saved sentence is no longer an error, `uncovered` for a new error state, and `moved` when a sentence now
566
+ reaches a different state. An existing v1 numeric-state file is accepted and migrated on update. Use `--max-tokens=N` and
567
+ `--max-configurations=N` to bound the shortest-sentence search; both default to the counterexample search limits.
568
+
569
+ Pass the reviewed file to Ruby generation with `--messages=grammar.messages`. Each matching message replaces only the generic
570
+ syntax-error sentence. `ParseError#error_id` exposes its stable `E00xx` identifier, and structured token, location,
571
+ expected-token, suggestion, source-line, and caret data remain available. A saved active sentence that no longer reaches an
572
+ error is rejected with an instruction to run the updater.
573
+
574
+ ## Analysis and visualizations
575
+
576
+ `ibex samples grammar.y` emits JSON token arrays derived from Grammar IR.
577
+ `--strategy=random|coverage` selects random alternatives or least-covered
578
+ production paths; `--path-length=1|2`, token, depth, expansion, count, and seed
579
+ options make the search finite and reproducible.
580
+
581
+ `ibex fuzz grammar.y` without `--against` feeds bounded generated sentences and
582
+ single-token insert/delete/replace mutations to SLR, LALR, IELR, and LR(1)
583
+ table simulators. It does not execute production actions. `--coverage-guided`
584
+ selects uncovered production paths. The JSON report records every effective
585
+ bound and returns 0 when no difference is found within those bounds, 1 with a
586
+ concrete differential witness, and 2 when a budget prevents completion.
587
+ `--format=text` renders the same result and bounds for a terminal; JSON remains
588
+ the default versioned contract.
589
+ `--against=COMMAND` is an explicit unsafe opt-in that sends each token array as
590
+ JSON to the supplied subprocess. It executes arbitrary application code with
591
+ the invoking user's host permissions and is not a sandbox. The command is
592
+ split into an executable and arguments without an implicit shell, but the
593
+ selected executable can itself access the filesystem, network, environment,
594
+ and other processes. Exit 0 means accepted and exit 1 means rejected. It
595
+ requires `--against-runtime=DESCRIPTION`, and the report records that target
596
+ runtime, the exact command, and the Ibex host runtime. Each invocation is
597
+ bounded by `--against-timeout=SECONDS` (default 10) and
598
+ `--against-max-output=N` (default 1,048,576 bytes); either limit exits 2
599
+ instead of treating a stuck or noisy target as a language result. These limits
600
+ and process-group cleanup bound resources but do not confine side effects. On
601
+ process-group platforms, descendants are terminated with the target. A
602
+ difference is automatically delta-minimized while preserving its mismatch kind
603
+ and outcomes, then written atomically to `test/fuzz/regressions` with its seed
604
+ and effective bounds.
605
+ `--max-reduction-trials=N` bounds that work, `--regression-dir=DIR` selects
606
+ another destination, and `--no-save-regression` disables persistence. An
607
+ incomplete minimization remains a concrete difference and records
608
+ `complete: false`; it is never described as minimal.
609
+
610
+ `ibex reduce --command=COMMAND input` is an explicit unsafe opt-in that
611
+ repeatedly executes the supplied checker to perform trial-bounded delta
612
+ debugging. The checker may run arbitrary application code with the invoking
613
+ user's host permissions; it is not sandboxed. As with external fuzzing,
614
+ resource limits and process cleanup do not confine filesystem, network,
615
+ process, or other side effects.
616
+ Input mode is `tokens` (a JSON string array), `lines`, or `bytes`. A nonzero
617
+ normal subprocess exit means the failure persists; a signal is an invocation
618
+ error rather than evidence. The version-2 report contains the reduced
619
+ sequence, trial count, original/final sizes, every effective limit, and whether
620
+ the configured trial budget allowed completion. A trial-limited result is
621
+ reported as `incomplete`, never as `minimized`. The checker timeout
622
+ (default 10 seconds), checker output (default 1,048,576 bytes), and input
623
+ (default 10 MiB) are bounded by `--timeout`, `--max-output-bytes`, and
624
+ `--max-input-bytes`. Exceeding any configured budget exits 2 with a distinct
625
+ reason. Report v1 remains the prior read-only contract.
626
+ `--format=text` selects a human-readable report; JSON remains the default.
627
+
628
+ `ibex verify automaton.json` validates table semantics by independently
629
+ deriving the relevant LR item collection from the embedded Grammar IR. Default
630
+ checks cover soundness, lookaheads, default reductions and explicit error
631
+ masks, reachability/productivity, epsilon termination, and conflict
632
+ resolution consistency. Both modes rebuild and compare plain/compact rows;
633
+ `--strict` adds complete collection checks and reports row mismatches under
634
+ the strict V5 identifier. `--max-states` and `--max-items` bound only the
635
+ reference derivation. JSON is the default report format; exit 0 means valid, 1
636
+ means a concrete violation, and 2 means the reference budget was exhausted.
637
+ Opaque semantic actions are never executed. See the
638
+ [verifier trust boundary](verifier-trust-boundary.md) for the exact TCB,
639
+ algorithm-specific strength, resource non-goals, and generated-artifact
640
+ boundary.
641
+
642
+ `ibex equiv LEFT RIGHT` accepts grammar source, Grammar IR, or Automaton IR.
643
+ It combines normalized structural comparison, deterministic samples generated
644
+ in both directions, and a breadth-first product search over the two LR state
645
+ stacks. A difference returns 1 with a shortest token witness found within
646
+ `--max-tokens` and `--max-configurations`; an incomplete search returns 2.
647
+ `--samples`, `--seed`, `--max-actions`, and `--max-stack` expose the remaining
648
+ bounds. `--map=old_rule=new_rule` requests reduction-tree comparison for a
649
+ declared one-to-one rule correspondence. Without a map, only language
650
+ acceptance is compared. A successful bounded search is explicitly not a proof
651
+ of equivalence.
652
+
653
+ `ibex diff OLD NEW` accepts the same three input forms and classifies symbols,
654
+ rules, conflicts, and normalization warnings into `added`, `removed`, and
655
+ `changed`. It also reports before/after/delta counts for states, productions,
656
+ warnings, and unresolved conflicts. Conflict identities use token and
657
+ production shapes rather than unstable state numbers.
658
+
659
+ `ibex metrics GRAMMAR` reports normalized rule and alternative counts, average
660
+ and maximum branching, epsilon productions, recursive nonterminals, the
661
+ longest dependency path after collapsing mutually recursive components, and
662
+ deterministic Automaton IR cell/conflict counts. The metric is deliberately
663
+ structural; it contains no timing or memory threshold. Both commands default
664
+ to their closed version-1 JSON schemas and support `--format=text`.
665
+
666
+ `ibex fix GRAMMAR` selects an unresolved conflict and searches a finite set of
667
+ precedence declarations and overrides, algorithm changes, and `%inline`
668
+ rewrites. Rewrites that would move opaque semantic actions, including
669
+ recursion reversal and source factoring, are deliberately excluded. A repair
670
+ proposal is emitted only if the target disappears, no other conflict fingerprint
671
+ increases, the resulting table passes the independent verifier, and bounded
672
+ language plus mapped reduction-tree comparison finds no difference. `%expect`
673
+ and recovery-quality suggestions are reported separately as non-repair advice;
674
+ they never enter the verified proposal or `--apply` path because they do not
675
+ eliminate the selected conflict. The JSON
676
+ report includes the exact bounds, unified diff, eliminated fingerprint, state
677
+ delta, and rejection reasons. `--messages=FILE` measures newly moved,
678
+ uncovered, and unreachable message-catalog entries against the original table.
679
+ `--apply[=FXNNN]` transactionally applies a source proposal and refuses
680
+ symlinks or files with multiple hard links. Candidate, independent-verifier,
681
+ or equivalence budget exhaustion exits 2. `--verify-max-states` and
682
+ `--verify-max-items` bound the independent collection used for every
683
+ candidate. The advice/repair separation and independent-verifier bounds are
684
+ reported by the closed `schema/fix-v3.schema.json`; versions 1 and 2 remain
685
+ available as prior report contracts. Every successful report states that
686
+ bounded search is not a proof of equivalence.
687
+
688
+ `ibex import bison [--format=source|json] [-o FILE] grammar.y` performs a
689
+ one-way, analysis-only conversion. C actions are mechanically reference-mapped
690
+ but remain opaque, and Ruby generation is refused. Unsupported directives are
691
+ reported at every source position; structural gaps are distinguished from
692
+ generator-only controls, and `fix` refuses structurally incomplete imports.
693
+ Input bytes, structural tokens, rule groups, and actions have independent
694
+ positive budgets. Read-only grammar analysis commands auto-detect two Bison
695
+ `%%` section markers. The complete directive, naming, external-corpus, and
696
+ CRuby `parse.y` contracts are in the
697
+ [Bison import guide](bison-import.md).
698
+
699
+ All CLI forms accept `--lang=LANG`; `IBEX_LANG` supplies the default. Built-in
700
+ diagnostic catalogs ship for `en` and `ja`. Locale suffixes such as
701
+ `ja_JP.UTF-8` select their base language, while unavailable translations fall
702
+ back to English silently. Stable machine-readable diagnostic codes and report
703
+ schemas do not change with the display language. The built-in
704
+ `diagnostic.*`, `warning.*`, `conflict.*`, and `note.*` IDs are an internal
705
+ translation namespace and never overlap the user-owned `E00xx` message IDs.
706
+
707
+ `--emit=sets` writes deterministic JSON containing nullable nonterminals and their FIRST and FOLLOW sets. `--dot=FILE` and
708
+ `--mermaid=FILE` write automaton graphs. `--html=FILE` writes a self-contained report with state search, conflict highlighting,
709
+ and a filter that keeps a selected conflict state and its one-hop neighbors. All three visualizations can be produced while
710
+ generating Ruby or when resuming from Automaton IR. `--railroad=FILE` writes a self-contained SVG railroad diagram from normalized
711
+ Grammar IR, so it is also available before automaton construction and when resuming from Grammar or Automaton IR.
712
+
713
+ `ibex explain grammar.y` is the focused conflict view. `--state=N` and `--token=NAME` select their intersection;
714
+ `--format=text|json` chooses step-by-step text or the version-1 document described by `schema/explain-v1.schema.json`.
715
+ `--algorithm=slr|lalr|ielr|lr1` selects construction, `--mode=default|extended` applies the same frontend mode as generation, and both
716
+ counterexample budget options bound its witness search. Search runs only after state and token selection and only for matching
717
+ conflicts. Token selectors prefer a canonical grammar name, then an exact unique display name. Unknown or ambiguous selectors
718
+ are errors; valid selectors with no matching conflict succeed with an empty result.
108
719
 
109
720
  ## Ruby DSL
110
721
 
@@ -123,5 +734,5 @@ end
123
734
  grammar_ir = Ibex::Normalizer.new(ast).normalize
124
735
  ```
125
736
 
126
- The builder also provides `options`, `expect`, `start`, `convert`, `user_code`, `ref(as:)`, `optional`, `star`, `plus`,
127
- `separated_list`, and `inline`.
737
+ The builder also provides `options`, `expect`, `start`, `convert`, `display`, `type`, `user_code`, `ref(as:)`, `optional`,
738
+ `star`, `plus`, `separated_list`, and `inline`.