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
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module TableArtifact
5
+ class TableValidator
6
+ include ValidationSupport
7
+
8
+ # @rbs!
9
+ # private def integer_array: (ValidationSupport::json_value value, String path,
10
+ # allow_nil: false, minimum: Integer?) -> Array[Integer]
11
+ # | (ValidationSupport::json_value value, String path,
12
+ # allow_nil: true, minimum: Integer?) -> Array[Integer?]
13
+
14
+ # @rbs (Hash[String, ValidationSupport::json_value] data, state_count: Integer, production_count: Integer,
15
+ # terminal_ids: Set[Integer], nonterminal_ids: Set[Integer], representation: String) -> void
16
+ def initialize(data, state_count:, production_count:, terminal_ids:, nonterminal_ids:, representation:)
17
+ @data = data
18
+ @state_count = state_count
19
+ @production_count = production_count
20
+ @terminal_ids = terminal_ids
21
+ @nonterminal_ids = nonterminal_ids
22
+ @representation = representation
23
+ end
24
+
25
+ # @rbs () -> void
26
+ def validate!
27
+ record(@data, "$.payload.tables", %w[actions gotos default_actions])
28
+ validate_actions(@data.fetch("actions"))
29
+ validate_gotos(@data.fetch("gotos"))
30
+ validate_defaults(@data.fetch("default_actions"))
31
+ end
32
+
33
+ private
34
+
35
+ # @rbs (ValidationSupport::json_value table) -> void
36
+ def validate_actions(table)
37
+ invalid("$.payload.tables.actions", "must be an object") unless table.is_a?(Hash)
38
+ table_data = table #: Hash[String, ValidationSupport::json_value]
39
+ encoding = table_data["encoding"]
40
+ expected = @representation == "compact" ? "signed-row-displacement-v1" : "signed-sparse-rows-v1"
41
+ invalid("$.payload.tables.actions.encoding", "does not match table representation") unless encoding == expected
42
+ case encoding
43
+ when "signed-sparse-rows-v1" then validate_sparse_actions(table_data)
44
+ when "signed-row-displacement-v1" then validate_compact_actions(table_data)
45
+ else invalid("$.payload.tables.actions.encoding", "is unsupported")
46
+ end
47
+ end
48
+
49
+ # @rbs (Hash[String, ValidationSupport::json_value] table) -> void
50
+ def validate_sparse_actions(table)
51
+ record(table, "$.payload.tables.actions", %w[encoding rows])
52
+ rows = array(table.fetch("rows"), "$.payload.tables.actions.rows")
53
+ invalid("$.payload.tables.actions.rows", "must contain one row per state") unless rows.length == @state_count
54
+ rows.each_with_index do |row, state|
55
+ token_ids = array(row, "$.payload.tables.actions.rows[#{state}]").map.with_index do |cell, index|
56
+ path = "$.payload.tables.actions.rows[#{state}][#{index}]"
57
+ cell = record(cell, path, %w[token_id code])
58
+ token_id = integer(cell.fetch("token_id"), "#{path}.token_id", minimum: 0)
59
+ invalid("#{path}.token_id", "must reference a terminal") unless @terminal_ids.include?(token_id)
60
+ validate_token_action(cell.fetch("code"), token_id, "#{path}.code")
61
+ token_id
62
+ end
63
+ sorted_unique!(token_ids, "$.payload.tables.actions.rows[#{state}]")
64
+ end
65
+ end
66
+
67
+ # @rbs (Hash[String, ValidationSupport::json_value] table) -> void
68
+ def validate_compact_actions(table)
69
+ keys = %w[encoding row_count column_count offsets codes checks]
70
+ record(table, "$.payload.tables.actions", keys)
71
+ enum(table.fetch("encoding"), "$.payload.tables.actions.encoding", ["signed-row-displacement-v1"])
72
+ validate_row_count(table, "$.payload.tables.actions")
73
+ column_count = nullable_integer(table.fetch("column_count"), "$.payload.tables.actions.column_count",
74
+ minimum: 1)
75
+ rows = validate_displacement(table, value_key: "codes", path: "$.payload.tables.actions") do |column, code,
76
+ cell_path|
77
+ invalid(cell_path, "column must reference a terminal") unless @terminal_ids.include?(column)
78
+ invalid(cell_path, "column exceeds column_count") if column_count && column >= column_count
79
+ validate_token_action(code, column, "#{cell_path}.code")
80
+ end
81
+ validate_canonical_displacement(
82
+ table, rows, value_key: "codes", width_key: "column_count", width: column_count, dense: false,
83
+ path: "$.payload.tables.actions"
84
+ )
85
+ end
86
+
87
+ # @rbs (ValidationSupport::json_value table) -> void
88
+ def validate_gotos(table)
89
+ invalid("$.payload.tables.gotos", "must be an object") unless table.is_a?(Hash)
90
+ table_data = table #: Hash[String, ValidationSupport::json_value]
91
+ encoding = table_data["encoding"]
92
+ expected = @representation == "compact" ? "row-displacement-v1" : "sparse-rows-v1"
93
+ invalid("$.payload.tables.gotos.encoding", "does not match table representation") unless encoding == expected
94
+ case encoding
95
+ when "sparse-rows-v1" then validate_sparse_gotos(table_data)
96
+ when "row-displacement-v1" then validate_compact_gotos(table_data)
97
+ else invalid("$.payload.tables.gotos.encoding", "is unsupported")
98
+ end
99
+ end
100
+
101
+ # @rbs (Hash[String, ValidationSupport::json_value] table) -> void
102
+ def validate_sparse_gotos(table)
103
+ record(table, "$.payload.tables.gotos", %w[encoding rows])
104
+ rows = array(table.fetch("rows"), "$.payload.tables.gotos.rows")
105
+ invalid("$.payload.tables.gotos.rows", "must contain one row per state") unless rows.length == @state_count
106
+ rows.each_with_index do |row, state|
107
+ symbol_ids = array(row, "$.payload.tables.gotos.rows[#{state}]").map.with_index do |cell, index|
108
+ path = "$.payload.tables.gotos.rows[#{state}][#{index}]"
109
+ cell = record(cell, path, %w[symbol_id state])
110
+ symbol_id = integer(cell.fetch("symbol_id"), "#{path}.symbol_id", minimum: 0)
111
+ invalid("#{path}.symbol_id", "must reference a nonterminal") unless @nonterminal_ids.include?(symbol_id)
112
+ validate_state(cell.fetch("state"), "#{path}.state")
113
+ symbol_id
114
+ end
115
+ sorted_unique!(symbol_ids, "$.payload.tables.gotos.rows[#{state}]")
116
+ end
117
+ end
118
+
119
+ # @rbs (Hash[String, ValidationSupport::json_value] table) -> void
120
+ def validate_compact_gotos(table)
121
+ keys = %w[encoding row_count dense_width offsets values checks]
122
+ record(table, "$.payload.tables.gotos", keys)
123
+ enum(table.fetch("encoding"), "$.payload.tables.gotos.encoding", ["row-displacement-v1"])
124
+ validate_row_count(table, "$.payload.tables.gotos")
125
+ dense_width = nullable_integer(table.fetch("dense_width"), "$.payload.tables.gotos.dense_width", minimum: 1)
126
+ rows = validate_displacement(table, value_key: "values", path: "$.payload.tables.gotos") do |column, state,
127
+ cell_path|
128
+ invalid(cell_path, "column must reference a nonterminal") unless @nonterminal_ids.include?(column)
129
+ invalid(cell_path, "column exceeds dense_width") if dense_width && column >= dense_width
130
+ validate_state(state, "#{cell_path}.state")
131
+ end
132
+ validate_canonical_displacement(
133
+ table, rows, value_key: "values", width_key: "dense_width", width: dense_width, dense: true,
134
+ path: "$.payload.tables.gotos"
135
+ )
136
+ end
137
+
138
+ # @rbs (Hash[String, ValidationSupport::json_value] table, String path) -> void
139
+ def validate_row_count(table, path)
140
+ count = integer(table.fetch("row_count"), "#{path}.row_count", minimum: 1)
141
+ invalid("#{path}.row_count", "must equal state_count") unless count == @state_count
142
+ end
143
+
144
+ # @rbs (Hash[String, ValidationSupport::json_value] table, value_key: String, path: String)
145
+ # { (Integer, ValidationSupport::json_value, String) -> void } -> Array[Hash[Integer, ValidationSupport::json_value]]
146
+ def validate_displacement(table, value_key:, path:)
147
+ offsets = integer_array(table.fetch("offsets"), "#{path}.offsets", allow_nil: false, minimum: 0)
148
+ invalid("#{path}.offsets", "must contain one offset per state") unless offsets.length == @state_count
149
+ minimum = value_key == "codes" ? nil : 0
150
+ values = integer_array(table.fetch(value_key), "#{path}.#{value_key}", allow_nil: true, minimum: minimum)
151
+ checks = integer_array(table.fetch("checks"), "#{path}.checks", allow_nil: true, minimum: 0)
152
+ invalid(path, "#{value_key} and checks must have equal length") unless values.length == checks.length
153
+
154
+ rows = Array.new(@state_count) { {} } #: Array[Hash[Integer, ValidationSupport::json_value]]
155
+ checks.each_index do |index|
156
+ row = checks[index]
157
+ value = values[index]
158
+ invalid("#{path}[#{index}]", "value and check occupancy must match") unless row.nil? == value.nil?
159
+ next unless row
160
+
161
+ invalid("#{path}.checks[#{index}]", "references a missing state") unless row.between?(0, @state_count - 1)
162
+ column = index - offsets.fetch(row)
163
+ invalid("#{path}[#{index}]", "has a negative column") if column.negative?
164
+ yield(column, value, "#{path}[#{index}]")
165
+ rows.fetch(row)[column] = value
166
+ end
167
+ rows
168
+ end
169
+
170
+ # @rbs (Hash[String, ValidationSupport::json_value] table,
171
+ # Array[Hash[Integer, ValidationSupport::json_value]] rows, value_key: String,
172
+ # width_key: String, width: Integer?, dense: bool, path: String) -> void
173
+ def validate_canonical_displacement(table, rows, value_key:, width_key:, width:, dense:, path:)
174
+ canonical = Tables::Compact.build(rows, dense: dense)
175
+ expected = [canonical.offsets, canonical.values, canonical.checks]
176
+ actual = [table.fetch("offsets"), table.fetch(value_key), table.fetch("checks")]
177
+ invalid(path, "must use the canonical minimal row-displacement layout") unless actual == expected
178
+
179
+ expected_width = if width_key == "column_count"
180
+ rows.flat_map(&:keys).max.to_i + 1
181
+ else
182
+ canonical.dense_width
183
+ end
184
+ expected_width = nil if expected_width && (@state_count * expected_width) > Tables::Compact::DENSE_CELL_LIMIT
185
+ invalid("#{path}.#{width_key}", "does not match the canonical table width") unless width == expected_width
186
+ end
187
+
188
+ # @rbs skip
189
+ def integer_array(value, path, allow_nil:, minimum:)
190
+ array(value, path).map.with_index do |item, index|
191
+ next if allow_nil && item.nil?
192
+
193
+ integer(item, "#{path}[#{index}]", minimum: minimum)
194
+ end
195
+ end
196
+
197
+ # @rbs (ValidationSupport::json_value defaults) -> void
198
+ def validate_defaults(defaults)
199
+ values = array(defaults, "$.payload.tables.default_actions")
200
+ unless values.length == @state_count
201
+ invalid("$.payload.tables.default_actions",
202
+ "must contain one entry per state")
203
+ end
204
+ values.each_with_index do |code, state|
205
+ next if code.nil?
206
+
207
+ path = "$.payload.tables.default_actions[#{state}]"
208
+ code = integer(code, path)
209
+ invalid(path, "must be an error or reduction") if code >= 0
210
+ end
211
+ end
212
+
213
+ # @rbs (ValidationSupport::json_value code, Integer token_id, String path) -> void
214
+ def validate_token_action(code, token_id, path)
215
+ code = integer(code, path)
216
+ invalid(path, "accept is only valid for $eof") if code.zero? && !token_id.zero?
217
+ invalid(path, "$eof cannot be shifted") if token_id.zero? && code.positive?
218
+ validate_action_code(code, path)
219
+ end
220
+
221
+ # @rbs (ValidationSupport::json_value code, String path) -> void
222
+ def validate_action_code(code, path)
223
+ code = integer(code, path)
224
+ return if [0, -1].include?(code)
225
+ return validate_state(code - 1, path) if code.positive?
226
+
227
+ production = -2 - code
228
+ invalid(path, "references a missing production") unless production.between?(0, @production_count - 1)
229
+ end
230
+
231
+ # @rbs (ValidationSupport::json_value state, String path) -> void
232
+ def validate_state(state, path)
233
+ state = integer(state, path, minimum: 0)
234
+ invalid(path, "references a missing state") unless state.between?(0, @state_count - 1)
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "validator/support"
4
+ require_relative "validator/tables"
5
+ require_relative "validator/metadata"
6
+
7
+ module Ibex
8
+ module TableArtifact
9
+ # Closed structural and referential validation for untrusted sidecars.
10
+ class Validator
11
+ include ValidationSupport
12
+
13
+ ROOT_KEYS = %w[artifact_type schema_version identity payload cost].freeze #: Array[String]
14
+ PAYLOAD_KEYS = %w[
15
+ table_format source state_count symbols tokens entry_states tables productions cst recovery semantic_actions
16
+ ].freeze #: Array[String]
17
+
18
+ class << self
19
+ # @rbs (Hash[String, ValidationSupport::json_value] data) -> true
20
+ def validate!(data)
21
+ new(data).validate!
22
+ end
23
+ end
24
+
25
+ # @rbs (Hash[String, ValidationSupport::json_value] data) -> void
26
+ def initialize(data)
27
+ @data = data
28
+ end
29
+
30
+ # rubocop:disable Naming/PredicateMethod -- bang denotes fail-fast validation.
31
+ # @rbs () -> true
32
+ def validate!
33
+ record(@data, "$", ROOT_KEYS)
34
+ payload = record(@data.fetch("payload"), "$.payload", PAYLOAD_KEYS)
35
+ validate_root_identity
36
+ representation = validate_table_format(payload.fetch("table_format"))
37
+ validate_source(payload.fetch("source"))
38
+ state_count = integer(payload.fetch("state_count"), "$.payload.state_count", minimum: 1)
39
+ symbols = validate_symbols(payload.fetch("symbols"))
40
+ validate_tokens(payload.fetch("tokens"), symbols)
41
+ validate_entries(payload.fetch("entry_states"), symbols, state_count)
42
+ productions = validate_productions(payload.fetch("productions"), symbols)
43
+ tables = payload.fetch("tables") #: Hash[String, ValidationSupport::json_value]
44
+ TableValidator.new(
45
+ tables,
46
+ state_count: state_count,
47
+ production_count: productions.length,
48
+ terminal_ids: symbol_ids(symbols, "terminal"),
49
+ nonterminal_ids: symbol_ids(symbols, "nonterminal"),
50
+ representation: representation
51
+ ).validate!
52
+ MetadataValidator.new(payload, symbols: symbols, productions: productions).validate!
53
+ validate_cost(@data.fetch("cost"), payload)
54
+ true
55
+ end
56
+ # rubocop:enable Naming/PredicateMethod
57
+
58
+ private
59
+
60
+ # @rbs () -> void
61
+ def validate_root_identity
62
+ invalid("$.artifact_type", "is unsupported") unless @data.fetch("artifact_type") == ARTIFACT_TYPE
63
+ invalid("$.schema_version", "is unsupported") unless @data.fetch("schema_version") == SCHEMA_VERSION
64
+ identity_keys = %w[
65
+ grammar_digest automaton_digest payload_digest cst_metadata_digest recovery_metadata_digest
66
+ ]
67
+ identity = record(@data.fetch("identity"), "$.identity", identity_keys)
68
+ identity.each { |key, value| digest(value, "$.identity.#{key}") }
69
+ expected = Serializer.digest(@data.fetch("payload"))
70
+ invalid("$.identity.payload_digest", "does not match the canonical payload") unless
71
+ identity.fetch("payload_digest") == expected
72
+ payload = @data.fetch("payload")
73
+ validate_named_digest(identity, payload, "cst_metadata", "cst")
74
+ validate_named_digest(identity, payload, "recovery_metadata", "recovery")
75
+ end
76
+
77
+ # @rbs (ValidationSupport::json_value data) -> String
78
+ def validate_table_format(data)
79
+ path = "$.payload.table_format"
80
+ keys = %w[family version representation action_encoding goto_encoding]
81
+ data = record(data, path, keys)
82
+ enum(data.fetch("family"), "#{path}.family", ["ibex-runtime-parser-table"])
83
+ invalid("#{path}.version", "must be 6") unless integer(data.fetch("version"), "#{path}.version") == 6
84
+ representation = enum(data.fetch("representation"), "#{path}.representation", %w[plain compact]) #: String
85
+ expected = if representation == "compact"
86
+ %w[signed-row-displacement-v1 row-displacement-v1]
87
+ else
88
+ %w[signed-sparse-rows-v1 sparse-rows-v1]
89
+ end
90
+ actual = [data.fetch("action_encoding"), data.fetch("goto_encoding")]
91
+ invalid(path, "encodings do not match representation") unless actual == expected
92
+ representation
93
+ end
94
+
95
+ # @rbs (ValidationSupport::json_value data) -> void
96
+ def validate_source(data)
97
+ source = record(data, "$.payload.source", %w[grammar_digest automaton_digest])
98
+ source.each { |key, value| digest(value, "$.payload.source.#{key}") }
99
+ identity = @data.fetch("identity")
100
+ invalid("$.payload.source", "must match root identity") unless
101
+ source.fetch("grammar_digest") == identity.fetch("grammar_digest") &&
102
+ source.fetch("automaton_digest") == identity.fetch("automaton_digest")
103
+ end
104
+
105
+ # @rbs (ValidationSupport::json_value data) -> Array[Hash[String, ValidationSupport::json_value]]
106
+ def validate_symbols(data)
107
+ symbols = array(data, "$.payload.symbols") #: Array[Hash[String, ValidationSupport::json_value]]
108
+ invalid("$.payload.symbols", "must not be empty") if symbols.empty?
109
+ names = [] #: Array[String]
110
+ symbols.each_with_index do |symbol, index|
111
+ path = "$.payload.symbols[#{index}]"
112
+ record(symbol, path, %w[id name kind display_name])
113
+ invalid("#{path}.id", "must be contiguous and ordered") unless symbol.fetch("id") == index
114
+ names << string(symbol.fetch("name"), "#{path}.name")
115
+ enum(symbol.fetch("kind"), "#{path}.kind", %w[terminal nonterminal])
116
+ nullable_string(symbol.fetch("display_name"), "#{path}.display_name")
117
+ end
118
+ invalid("$.payload.symbols", "names must be unique") unless names.uniq.length == names.length
119
+ symbols
120
+ end
121
+
122
+ # @rbs (ValidationSupport::json_value data, Array[Hash[String, ValidationSupport::json_value]] symbols) -> void
123
+ def validate_tokens(data, symbols)
124
+ tokens = array(data, "$.payload.tokens") #: Array[Hash[String, ValidationSupport::json_value]]
125
+ expected = symbols.select { |symbol| symbol.fetch("kind") == "terminal" }.map do |symbol|
126
+ symbol.slice("id", "name", "display_name")
127
+ end
128
+ tokens.each_with_index do |token, index|
129
+ record(token, "$.payload.tokens[#{index}]", %w[id name display_name])
130
+ end
131
+ invalid("$.payload.tokens", "must exactly index terminal symbols") unless tokens == expected
132
+ token_id = tokens.first&.fetch("id")
133
+ invalid("$.payload.tokens", "token id 0 must be $eof") unless token_id.is_a?(Integer) && token_id.zero? &&
134
+ tokens.first.fetch("name") == "$eof"
135
+ end
136
+
137
+ # @rbs (ValidationSupport::json_value data,
138
+ # Array[Hash[String, ValidationSupport::json_value]] symbols, Integer state_count) -> void
139
+ def validate_entries(data, symbols, state_count)
140
+ entries = array(data, "$.payload.entry_states") #: Array[Hash[String, ValidationSupport::json_value]]
141
+ invalid("$.payload.entry_states", "must not be empty") if entries.empty?
142
+ names = entries.map.with_index do |entry, index|
143
+ path = "$.payload.entry_states[#{index}]"
144
+ record(entry, path, %w[name state])
145
+ name = string(entry.fetch("name"), "#{path}.name")
146
+ symbol = symbols.find { |candidate| candidate.fetch("name") == name }
147
+ invalid("#{path}.name", "must reference a nonterminal") unless symbol&.fetch("kind") == "nonterminal"
148
+ state = integer(entry.fetch("state"), "#{path}.state", minimum: 0)
149
+ invalid("#{path}.state", "references a missing state") unless state < state_count
150
+ name
151
+ end
152
+ invalid("$.payload.entry_states", "names must be unique") unless names.uniq.length == names.length
153
+ end
154
+
155
+ # @rbs (ValidationSupport::json_value data,
156
+ # Array[Hash[String, ValidationSupport::json_value]] symbols) -> Array[Hash[String, ValidationSupport::json_value]]
157
+ def validate_productions(data, symbols)
158
+ productions = array(data, "$.payload.productions") #: Array[Hash[String, ValidationSupport::json_value]]
159
+ nonterminals = symbol_ids(symbols, "nonterminal")
160
+ productions.each_with_index do |production, index|
161
+ path = "$.payload.productions[#{index}]"
162
+ record(production, path, %w[id lhs rhs rhs_length action_slot])
163
+ invalid("#{path}.id", "must be contiguous and ordered") unless production.fetch("id") == index
164
+ lhs = integer(production.fetch("lhs"), "#{path}.lhs", minimum: 0)
165
+ invalid("#{path}.lhs", "must reference a nonterminal") unless nonterminals.include?(lhs)
166
+ rhs = array(production.fetch("rhs"), "#{path}.rhs")
167
+ rhs.each_with_index do |symbol_id, rhs_index|
168
+ symbol_id = integer(symbol_id, "#{path}.rhs[#{rhs_index}]", minimum: 0)
169
+ invalid("#{path}.rhs[#{rhs_index}]", "references a missing symbol") unless symbol_id < symbols.length
170
+ end
171
+ invalid("#{path}.rhs_length", "must match rhs") unless production.fetch("rhs_length") == rhs.length
172
+ slot = nullable_integer(production.fetch("action_slot"), "#{path}.action_slot", minimum: 0)
173
+ invalid("#{path}.action_slot", "must equal production id") if slot && slot != index
174
+ end
175
+ productions
176
+ end
177
+
178
+ # @rbs (ValidationSupport::json_value data, Hash[String, ValidationSupport::json_value] payload) -> void
179
+ def validate_cost(data, payload)
180
+ path = "$.cost"
181
+ keys = %w[
182
+ canonical_payload_bytes action_cells goto_cells lookup_cost recognition_cost measurement bounded_by_max_steps
183
+ ]
184
+ data = record(data, path, keys)
185
+ bytes = integer(data.fetch("canonical_payload_bytes"), "#{path}.canonical_payload_bytes", minimum: 1)
186
+ unless bytes == Serializer.compact(payload).bytesize
187
+ invalid("#{path}.canonical_payload_bytes",
188
+ "does not match payload")
189
+ end
190
+ action_cells = integer(data.fetch("action_cells"), "#{path}.action_cells", minimum: 0)
191
+ goto_cells = integer(data.fetch("goto_cells"), "#{path}.goto_cells", minimum: 0)
192
+ tables = payload.fetch("tables") #: Hash[String, ValidationSupport::json_value]
193
+ actions = tables.fetch("actions") #: Hash[String, ValidationSupport::json_value]
194
+ gotos = tables.fetch("gotos") #: Hash[String, ValidationSupport::json_value]
195
+ invalid("#{path}.action_cells", "does not match actions") unless action_cells == occupied(actions, "codes")
196
+ invalid("#{path}.goto_cells", "does not match gotos") unless goto_cells == occupied(gotos, "values")
197
+ expected_lookup, expected_recognition = expected_costs(payload)
198
+ invalid("#{path}.lookup_cost", "does not match table representation") unless
199
+ data.fetch("lookup_cost") == expected_lookup
200
+ invalid("#{path}.recognition_cost", "does not match table representation") unless
201
+ data.fetch("recognition_cost") == expected_recognition
202
+ enum(data.fetch("measurement"), "#{path}.measurement", ["not-measured"])
203
+ invalid("#{path}.bounded_by_max_steps", "must be true") unless boolean(data.fetch("bounded_by_max_steps"),
204
+ "#{path}.bounded_by_max_steps")
205
+ end
206
+
207
+ # @rbs (Hash[String, ValidationSupport::json_value] payload) -> [String, String]
208
+ def expected_costs(payload)
209
+ table_format = payload.fetch("table_format") #: Hash[String, ValidationSupport::json_value]
210
+ compact = table_format.fetch("representation") == "compact"
211
+ lookup = compact ? "O(1) row-displacement probe" : "O(log row width) binary search"
212
+ recognition = if compact
213
+ "O(input tokens + reductions)"
214
+ else
215
+ "O((input tokens + reductions) * log maximum row width)"
216
+ end
217
+ [lookup, "#{recognition}; lexer, recovery search, and semantic actions excluded"]
218
+ end
219
+
220
+ # @rbs (Hash[String, ValidationSupport::json_value] table, String values_key) -> Integer
221
+ def occupied(table, values_key)
222
+ if table.key?("rows")
223
+ rows = table.fetch("rows") #: Array[Array[ValidationSupport::json_value]]
224
+ return rows.sum(&:length)
225
+ end
226
+
227
+ values = table.fetch(values_key) #: Array[ValidationSupport::json_value]
228
+ values.compact.length
229
+ end
230
+
231
+ # @rbs (Array[Hash[String, ValidationSupport::json_value]] symbols, String kind) -> Set[Integer]
232
+ def symbol_ids(symbols, kind)
233
+ symbols.filter_map do |symbol|
234
+ next unless symbol.fetch("kind") == kind
235
+
236
+ symbol.fetch("id") #: Integer
237
+ end.to_set
238
+ end
239
+
240
+ # @rbs (Hash[String, ValidationSupport::json_value] identity,
241
+ # Hash[String, ValidationSupport::json_value] payload, String identity_name,
242
+ # String payload_name) -> void
243
+ def validate_named_digest(identity, payload, identity_name, payload_name)
244
+ expected = Serializer.digest(payload.fetch(payload_name))
245
+ path = "$.identity.#{identity_name}_digest"
246
+ return if identity.fetch("#{identity_name}_digest") == expected
247
+
248
+ invalid(path,
249
+ "does not match payload #{payload_name}")
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require "set"
6
+ require_relative "error"
7
+ require_relative "ir"
8
+ require_relative "tables"
9
+ require_relative "runtime/table_format"
10
+ require_relative "codegen/cst_metadata"
11
+ require_relative "table_artifact/serializer"
12
+ require_relative "table_artifact/validator"
13
+ require_relative "table_artifact/document"
14
+ require_relative "table_artifact/cst_projection"
15
+ require_relative "table_artifact/builder"
16
+ require_relative "table_artifact/executor"
17
+
18
+ module Ibex
19
+ # Experimental data-only authority for executable parser tables.
20
+ module TableArtifact
21
+ # @rbs!
22
+ # interface _Reader
23
+ # def read: (Integer length) -> String?
24
+ # end
25
+
26
+ ARTIFACT_TYPE = "ibex.parser-table" #: String
27
+ SCHEMA_VERSION = 1 #: Integer
28
+ DEFAULT_MAX_BYTES = 16 * 1024 * 1024 #: Integer
29
+
30
+ class ValidationError < Ibex::Error; end
31
+
32
+ class << self
33
+ # @rbs (IR::Automaton automaton, ?representation: Symbol | String, ?cst_trivia: Symbol | String?,
34
+ # ?omit_action_call: bool?) -> Document
35
+ def build(automaton, representation: :compact, cst_trivia: nil, omit_action_call: nil)
36
+ Builder.new(
37
+ automaton,
38
+ representation: representation,
39
+ cst_trivia: cst_trivia,
40
+ omit_action_call: omit_action_call
41
+ ).build
42
+ end
43
+
44
+ # @rbs (String | _Reader source, ?max_bytes: Integer) -> Document
45
+ def load(source, max_bytes: DEFAULT_MAX_BYTES)
46
+ bytes = read_bounded(source, max_bytes)
47
+ unless bytes.dup.force_encoding(Encoding::UTF_8).valid_encoding?
48
+ raise ValidationError, "table artifact must be UTF-8 JSON"
49
+ end
50
+
51
+ Document.new(JSON.parse(bytes))
52
+ rescue JSON::ParserError => e
53
+ raise ValidationError, "invalid table artifact JSON: #{e.message}"
54
+ end
55
+
56
+ private
57
+
58
+ # @rbs (untyped source, Integer max_bytes) -> String
59
+ def read_bounded(source, max_bytes)
60
+ raise ArgumentError, "max_bytes must be positive" unless max_bytes.is_a?(Integer) && max_bytes.positive?
61
+
62
+ bytes = if source.respond_to?(:read)
63
+ read_from(source, max_bytes)
64
+ else
65
+ String(source)
66
+ end
67
+ raise ValidationError, "table artifact exceeds #{max_bytes} bytes" if bytes.bytesize > max_bytes
68
+
69
+ bytes
70
+ end
71
+
72
+ # @rbs (_Reader source, Integer max_bytes) -> String
73
+ def read_from(source, max_bytes)
74
+ bytes = +"".b
75
+ loop do
76
+ chunk = source.read((max_bytes + 1) - bytes.bytesize)
77
+ break if chunk.nil?
78
+ raise ValidationError, "table artifact reader must return strings" unless chunk.is_a?(String)
79
+ break if chunk.empty?
80
+
81
+ bytes << chunk
82
+ break if bytes.bytesize > max_bytes
83
+ end
84
+ bytes
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+
6
+ module Ibex
7
+ module TableSimulation
8
+ # Final immutable simulation document.
9
+ class Result
10
+ # @rbs!
11
+ # type step_document = Hash[String, Step::document_value]
12
+ # type document_value = String | Integer | Array[String] | Array[step_document]
13
+
14
+ IDENTIFIER = "table-simulation" #: String
15
+ SCHEMA_VERSION = 1 #: Integer
16
+
17
+ attr_reader :grammar_digest #: String
18
+ attr_reader :algorithm #: String
19
+ attr_reader :tokens #: Array[String]
20
+ attr_reader :status #: Symbol
21
+ attr_reader :steps #: Array[Step]
22
+
23
+ # @rbs (grammar_digest: String, algorithm: String, tokens: Array[String], status: Symbol,
24
+ # steps: Array[Step]) -> void
25
+ def initialize(grammar_digest:, algorithm:, tokens:, status:, steps:)
26
+ raise ArgumentError, "simulation status must be accepted or error" unless %i[accepted error].include?(status)
27
+
28
+ @grammar_digest = grammar_digest.dup.freeze
29
+ @algorithm = algorithm.dup.freeze
30
+ @tokens = tokens.map { |token| token.dup.freeze }.freeze
31
+ @status = status
32
+ @steps = steps.dup.freeze
33
+ freeze
34
+ end
35
+
36
+ # @rbs () -> Hash[String, document_value]
37
+ def to_h
38
+ {
39
+ "ibex_table_simulation" => IDENTIFIER,
40
+ "schema_version" => SCHEMA_VERSION,
41
+ "grammar_digest" => @grammar_digest,
42
+ "algorithm" => @algorithm,
43
+ "tokens" => @tokens,
44
+ "status" => @status.to_s,
45
+ "steps" => @steps.map(&:to_h)
46
+ }
47
+ end
48
+
49
+ # @rbs (*Object?) -> String
50
+ def to_json(*)
51
+ "#{JSON.pretty_generate(to_h)}\n"
52
+ end
53
+ end
54
+ end
55
+ end