ibex 0.1.0 → 0.2.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 (384) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +247 -104
  3. data/docs/architecture.md +322 -28
  4. data/docs/cst-migration.md +107 -0
  5. data/docs/cst.md +213 -0
  6. data/docs/development.md +129 -0
  7. data/docs/editor-setup.md +25 -0
  8. data/docs/error-ux.md +60 -0
  9. data/docs/grammar-reference.md +458 -18
  10. data/docs/lexer-migration.md +51 -0
  11. data/docs/racc-migration.md +34 -6
  12. data/docs/release-readiness.md +163 -0
  13. data/docs/stability.md +124 -0
  14. data/examples/README.md +60 -0
  15. data/examples/calculator.y +44 -0
  16. data/examples/csv.y +32 -0
  17. data/examples/ini.y +70 -0
  18. data/examples/json.y +48 -0
  19. data/examples/tiny_language.y +75 -0
  20. data/lib/ibex/analysis/sets.rb +5 -4
  21. data/lib/ibex/artifact_set.rb +63 -0
  22. data/lib/ibex/cli/ambiguity.rb +66 -0
  23. data/lib/ibex/cli/counterexample_options.rb +6 -2
  24. data/lib/ibex/cli/coverage.rb +173 -0
  25. data/lib/ibex/cli/debug.rb +106 -0
  26. data/lib/ibex/cli/diagnostics.rb +116 -0
  27. data/lib/ibex/cli/documentation.rb +67 -0
  28. data/lib/ibex/cli/error_messages.rb +177 -0
  29. data/lib/ibex/cli/explain.rb +76 -0
  30. data/lib/ibex/cli/formatting.rb +386 -0
  31. data/lib/ibex/cli/generation_artifacts.rb +138 -0
  32. data/lib/ibex/cli/generation_error_messages.rb +39 -0
  33. data/lib/ibex/cli/grammar_tests.rb +122 -0
  34. data/lib/ibex/cli/ir_tools.rb +184 -0
  35. data/lib/ibex/cli/lsp.rb +33 -0
  36. data/lib/ibex/cli/outputs.rb +91 -8
  37. data/lib/ibex/cli/racc_migration.rb +87 -0
  38. data/lib/ibex/cli/samples.rb +92 -0
  39. data/lib/ibex/cli/watch.rb +103 -0
  40. data/lib/ibex/cli.rb +506 -37
  41. data/lib/ibex/codegen/action_locations.rb +103 -0
  42. data/lib/ibex/codegen/action_method_source.rb +210 -0
  43. data/lib/ibex/codegen/action_source.rb +118 -0
  44. data/lib/ibex/codegen/ambiguity.rb +173 -0
  45. data/lib/ibex/codegen/cst_metadata.rb +171 -0
  46. data/lib/ibex/codegen/documentation.rb +170 -0
  47. data/lib/ibex/codegen/explain.rb +312 -0
  48. data/lib/ibex/codegen/generated_action_abi.rb +288 -0
  49. data/lib/ibex/codegen/html.rb +94 -10
  50. data/lib/ibex/codegen/mermaid.rb +43 -0
  51. data/lib/ibex/codegen/railroad.rb +197 -0
  52. data/lib/ibex/codegen/railroad_documentation.rb +59 -0
  53. data/lib/ibex/codegen/rbs.rb +323 -2
  54. data/lib/ibex/codegen/report.rb +46 -6
  55. data/lib/ibex/codegen/ruby.rb +334 -59
  56. data/lib/ibex/codegen/ruby_actions.rb +143 -0
  57. data/lib/ibex/codegen/ruby_ast.rb +121 -0
  58. data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
  59. data/lib/ibex/codegen/ruby_lexer.rb +83 -0
  60. data/lib/ibex/codegen/ruby_syntax.rb +90 -0
  61. data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
  62. data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
  63. data/lib/ibex/codegen/symbol_labels.rb +1 -1
  64. data/lib/ibex/coverage/collector.rb +188 -0
  65. data/lib/ibex/coverage/event_stream.rb +97 -0
  66. data/lib/ibex/coverage/report.rb +259 -0
  67. data/lib/ibex/coverage/runtime_event_validator.rb +160 -0
  68. data/lib/ibex/coverage.rb +13 -0
  69. data/lib/ibex/error_messages/parser.rb +159 -0
  70. data/lib/ibex/error_messages/parser_v2.rb +198 -0
  71. data/lib/ibex/error_messages/renderer.rb +65 -0
  72. data/lib/ibex/error_messages/sentence_search.rb +196 -0
  73. data/lib/ibex/error_messages/update.rb +169 -0
  74. data/lib/ibex/error_messages.rb +165 -0
  75. data/lib/ibex/frontend/ast.rb +145 -6
  76. data/lib/ibex/frontend/bootstrap_parser.rb +47 -4
  77. data/lib/ibex/frontend/diagnostic.rb +81 -0
  78. data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
  79. data/lib/ibex/frontend/dsl.rb +66 -7
  80. data/lib/ibex/frontend/formatter.rb +407 -0
  81. data/lib/ibex/frontend/generated_parser.rb +614 -190
  82. data/lib/ibex/frontend/generated_parser_base.rb +164 -30
  83. data/lib/ibex/frontend/generated_parser_includes.rb +61 -0
  84. data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
  85. data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
  86. data/lib/ibex/frontend/generation.rb +33 -0
  87. data/lib/ibex/frontend/lexer.rb +162 -10
  88. data/lib/ibex/frontend/lexer_recovery.rb +84 -0
  89. data/lib/ibex/frontend/parser/declarations.rb +247 -11
  90. data/lib/ibex/frontend/parser/parameters.rb +82 -0
  91. data/lib/ibex/frontend/parser/rules.rb +43 -6
  92. data/lib/ibex/frontend/parser.rb +182 -4
  93. data/lib/ibex/frontend/regenerator.rb +26 -1
  94. data/lib/ibex/frontend/resolution.rb +69 -0
  95. data/lib/ibex/frontend/resolver.rb +217 -0
  96. data/lib/ibex/frontend/rule_documentation.rb +103 -0
  97. data/lib/ibex/frontend/source_cursor.rb +132 -8
  98. data/lib/ibex/frontend/source_document.rb +229 -0
  99. data/lib/ibex/frontend/source_loader.rb +150 -0
  100. data/lib/ibex/frontend/source_span.rb +81 -0
  101. data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
  102. data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
  103. data/lib/ibex/frontend/token_adapter/declaration_state.rb +216 -26
  104. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
  105. data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
  106. data/lib/ibex/frontend/token_adapter.rb +8 -3
  107. data/lib/ibex/frontend.rb +13 -2
  108. data/lib/ibex/generation_input.rb +57 -0
  109. data/lib/ibex/generation_manifest.rb +200 -0
  110. data/lib/ibex/generation_transaction.rb +261 -0
  111. data/lib/ibex/generation_transaction_recovery.rb +109 -0
  112. data/lib/ibex/generation_transaction_validation.rb +196 -0
  113. data/lib/ibex/grammar_tests.rb +206 -0
  114. data/lib/ibex/ir/automaton_ir.rb +38 -5
  115. data/lib/ibex/ir/grammar_ir.rb +137 -24
  116. data/lib/ibex/ir/lexer_ir.rb +76 -0
  117. data/lib/ibex/ir/migration.rb +120 -0
  118. data/lib/ibex/ir/serialize.rb +110 -19
  119. data/lib/ibex/ir/validator/automaton.rb +345 -0
  120. data/lib/ibex/ir/validator/base.rb +129 -0
  121. data/lib/ibex/ir/validator/grammar.rb +604 -0
  122. data/lib/ibex/ir/validator/lexer.rb +113 -0
  123. data/lib/ibex/ir/validator.rb +62 -0
  124. data/lib/ibex/ir.rb +57 -4
  125. data/lib/ibex/lalr/build_metrics.rb +23 -0
  126. data/lib/ibex/lalr/builder.rb +379 -52
  127. data/lib/ibex/lalr/conflict.rb +1 -0
  128. data/lib/ibex/lalr/conflict_search.rb +11 -5
  129. data/lib/ibex/lalr/counterexample.rb +20 -5
  130. data/lib/ibex/lalr/direct_lookaheads.rb +236 -0
  131. data/lib/ibex/lalr/ielr_partition.rb +152 -0
  132. data/lib/ibex/lalr/on_error_reductions.rb +74 -0
  133. data/lib/ibex/lalr.rb +7 -0
  134. data/lib/ibex/location.rb +129 -0
  135. data/lib/ibex/lsp/document_handlers.rb +66 -0
  136. data/lib/ibex/lsp/document_store.rb +264 -0
  137. data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
  138. data/lib/ibex/lsp/document_store_validation.rb +61 -0
  139. data/lib/ibex/lsp/initialization_handlers.rb +78 -0
  140. data/lib/ibex/lsp/navigation_handlers.rb +56 -0
  141. data/lib/ibex/lsp/position_codec.rb +109 -0
  142. data/lib/ibex/lsp/protocol_error.rb +33 -0
  143. data/lib/ibex/lsp/request_handlers.rb +45 -0
  144. data/lib/ibex/lsp/request_support.rb +87 -0
  145. data/lib/ibex/lsp/server.rb +146 -0
  146. data/lib/ibex/lsp/symbol_index.rb +241 -0
  147. data/lib/ibex/lsp/symbol_index_builder.rb +267 -0
  148. data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
  149. data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
  150. data/lib/ibex/lsp/symbol_occurrence.rb +17 -0
  151. data/lib/ibex/lsp/transport.rb +118 -0
  152. data/lib/ibex/lsp/workspace.rb +127 -0
  153. data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
  154. data/lib/ibex/lsp.rb +32 -0
  155. data/lib/ibex/normalize/declarations.rb +140 -7
  156. data/lib/ibex/normalize/diagnostics.rb +50 -5
  157. data/lib/ibex/normalize/expander.rb +59 -55
  158. data/lib/ibex/normalize/expression.rb +60 -39
  159. data/lib/ibex/normalize/inline_expansion.rb +414 -0
  160. data/lib/ibex/normalize/inline_validation.rb +174 -0
  161. data/lib/ibex/normalize/lexer.rb +131 -0
  162. data/lib/ibex/normalize/named_references.rb +60 -0
  163. data/lib/ibex/normalize/nodes.rb +46 -0
  164. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +69 -0
  165. data/lib/ibex/normalize/parameter_lowering.rb +126 -0
  166. data/lib/ibex/normalize/parameter_substitution.rb +125 -0
  167. data/lib/ibex/normalize/parameter_validation.rb +140 -0
  168. data/lib/ibex/normalize/parameters.rb +199 -0
  169. data/lib/ibex/normalize/recovery_declarations.rb +84 -0
  170. data/lib/ibex/normalize.rb +179 -14
  171. data/lib/ibex/racc_migration/checker.rb +122 -0
  172. data/lib/ibex/racc_migration/harness.rb +177 -0
  173. data/lib/ibex/racc_migration/report.rb +94 -0
  174. data/lib/ibex/racc_migration.rb +12 -0
  175. data/lib/ibex/rake_task.rb +116 -0
  176. data/lib/ibex/samples.rb +186 -0
  177. data/lib/ibex/table_simulation/result.rb +51 -0
  178. data/lib/ibex/table_simulation/simulator.rb +253 -0
  179. data/lib/ibex/table_simulation/step.rb +60 -0
  180. data/lib/ibex/table_simulation/text.rb +31 -0
  181. data/lib/ibex/table_simulation.rb +13 -0
  182. data/lib/ibex/tables.rb +9 -70
  183. data/lib/ibex/version.rb +1 -1
  184. data/lib/ibex/watch/runner.rb +172 -0
  185. data/lib/ibex/watch/source_snapshot.rb +93 -0
  186. data/lib/ibex/watch.rb +11 -0
  187. data/lib/ibex.rb +25 -1
  188. data/schema/automaton-ir-v1.schema.json +401 -0
  189. data/schema/automaton-ir-v2.schema.json +58 -0
  190. data/schema/benchmark-v1.schema.json +212 -0
  191. data/schema/benchmark-v2.schema.json +61 -0
  192. data/schema/cst-v1.json +170 -0
  193. data/schema/error-ux-v1.schema.json +258 -0
  194. data/schema/explain-v1.schema.json +433 -0
  195. data/schema/frontend-diagnostics-v1.schema.json +154 -0
  196. data/schema/generation-manifest-v1.schema.json +115 -0
  197. data/schema/grammar-ir-v1.schema.json +426 -0
  198. data/schema/grammar-ir-v2.schema.json +779 -0
  199. data/schema/lexer-ir-v1.schema.json +215 -0
  200. data/schema/migration-check-v1.schema.json +60 -0
  201. data/schema/performance-comparison-v1.schema.json +395 -0
  202. data/schema/public-performance-comparison-v1.schema.json +506 -0
  203. data/schema/public-performance-profile-v1.schema.json +360 -0
  204. data/schema/runtime-coverage-v1.schema.json +86 -0
  205. data/schema/runtime-event-v1.schema.json +308 -0
  206. data/schema/table-simulation-v1.schema.json +70 -0
  207. data/sig/ibex/artifact_set.rbs +37 -0
  208. data/sig/ibex/cli/ambiguity.rbs +22 -0
  209. data/sig/ibex/cli/counterexample_options.rbs +2 -0
  210. data/sig/ibex/cli/coverage.rbs +53 -0
  211. data/sig/ibex/cli/debug.rbs +28 -0
  212. data/sig/ibex/cli/diagnostics.rbs +38 -0
  213. data/sig/ibex/cli/documentation.rbs +25 -0
  214. data/sig/ibex/cli/error_messages.rbs +57 -0
  215. data/sig/ibex/cli/explain.rbs +25 -0
  216. data/sig/ibex/cli/formatting.rbs +103 -0
  217. data/sig/ibex/cli/generation_artifacts.rbs +50 -0
  218. data/sig/ibex/cli/generation_error_messages.rbs +19 -0
  219. data/sig/ibex/cli/grammar_tests.rbs +36 -0
  220. data/sig/ibex/cli/ir_tools.rbs +51 -0
  221. data/sig/ibex/cli/lsp.rbs +14 -0
  222. data/sig/ibex/cli/outputs.rbs +17 -0
  223. data/sig/ibex/cli/racc_migration.rbs +30 -0
  224. data/sig/ibex/cli/samples.rbs +30 -0
  225. data/sig/ibex/cli/watch.rbs +43 -0
  226. data/sig/ibex/cli.rbs +104 -5
  227. data/sig/ibex/codegen/action_locations.rbs +45 -0
  228. data/sig/ibex/codegen/action_method_source.rbs +65 -0
  229. data/sig/ibex/codegen/action_source.rbs +50 -0
  230. data/sig/ibex/codegen/ambiguity.rbs +60 -0
  231. data/sig/ibex/codegen/cst_metadata.rbs +59 -0
  232. data/sig/ibex/codegen/documentation.rbs +50 -0
  233. data/sig/ibex/codegen/explain.rbs +85 -0
  234. data/sig/ibex/codegen/generated_action_abi.rbs +101 -0
  235. data/sig/ibex/codegen/html.rbs +18 -2
  236. data/sig/ibex/codegen/mermaid.rbs +16 -0
  237. data/sig/ibex/codegen/railroad.rbs +82 -0
  238. data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
  239. data/sig/ibex/codegen/rbs.rbs +84 -4
  240. data/sig/ibex/codegen/report.rbs +8 -0
  241. data/sig/ibex/codegen/ruby.rbs +97 -23
  242. data/sig/ibex/codegen/ruby_actions.rbs +54 -0
  243. data/sig/ibex/codegen/ruby_ast.rbs +34 -0
  244. data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
  245. data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
  246. data/sig/ibex/codegen/ruby_syntax.rbs +25 -0
  247. data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
  248. data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
  249. data/sig/ibex/coverage/collector.rbs +76 -0
  250. data/sig/ibex/coverage/event_stream.rbs +42 -0
  251. data/sig/ibex/coverage/report.rbs +100 -0
  252. data/sig/ibex/coverage/runtime_event_validator.rbs +68 -0
  253. data/sig/ibex/coverage.rbs +7 -0
  254. data/sig/ibex/error_messages/parser.rbs +58 -0
  255. data/sig/ibex/error_messages/parser_v2.rbs +67 -0
  256. data/sig/ibex/error_messages/renderer.rbs +23 -0
  257. data/sig/ibex/error_messages/sentence_search.rbs +80 -0
  258. data/sig/ibex/error_messages/update.rbs +43 -0
  259. data/sig/ibex/error_messages.rbs +85 -0
  260. data/sig/ibex/frontend/ast.rbs +208 -19
  261. data/sig/ibex/frontend/bootstrap_parser.rbs +11 -0
  262. data/sig/ibex/frontend/diagnostic.rbs +53 -0
  263. data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
  264. data/sig/ibex/frontend/dsl.rbs +33 -4
  265. data/sig/ibex/frontend/formatter.rbs +135 -0
  266. data/sig/ibex/frontend/generated_parser.rbs +218 -68
  267. data/sig/ibex/frontend/generated_parser_base.rbs +61 -10
  268. data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
  269. data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
  270. data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
  271. data/sig/ibex/frontend/generation.rbs +6 -0
  272. data/sig/ibex/frontend/lexer.rbs +49 -2
  273. data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
  274. data/sig/ibex/frontend/parser/declarations.rbs +54 -0
  275. data/sig/ibex/frontend/parser/parameters.rbs +28 -0
  276. data/sig/ibex/frontend/parser/rules.rbs +3 -0
  277. data/sig/ibex/frontend/parser.rbs +66 -0
  278. data/sig/ibex/frontend/regenerator.rbs +11 -0
  279. data/sig/ibex/frontend/resolution.rbs +33 -0
  280. data/sig/ibex/frontend/resolver.rbs +91 -0
  281. data/sig/ibex/frontend/rule_documentation.rbs +42 -0
  282. data/sig/ibex/frontend/source_cursor.rbs +36 -3
  283. data/sig/ibex/frontend/source_document.rbs +119 -0
  284. data/sig/ibex/frontend/source_loader.rbs +66 -0
  285. data/sig/ibex/frontend/source_span.rbs +53 -0
  286. data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
  287. data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
  288. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +73 -5
  289. data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
  290. data/sig/ibex/frontend/token_adapter.rbs +5 -2
  291. data/sig/ibex/frontend.rbs +1 -1
  292. data/sig/ibex/generation_input.rbs +37 -0
  293. data/sig/ibex/generation_manifest.rbs +67 -0
  294. data/sig/ibex/generation_transaction.rbs +82 -0
  295. data/sig/ibex/generation_transaction_recovery.rbs +36 -0
  296. data/sig/ibex/generation_transaction_validation.rbs +65 -0
  297. data/sig/ibex/grammar_tests.rbs +93 -0
  298. data/sig/ibex/ir/automaton_ir.rbs +8 -2
  299. data/sig/ibex/ir/grammar_ir.rbs +75 -15
  300. data/sig/ibex/ir/lexer_ir.rbs +57 -0
  301. data/sig/ibex/ir/migration.rbs +34 -0
  302. data/sig/ibex/ir/serialize.rbs +23 -6
  303. data/sig/ibex/ir/validator/automaton.rbs +109 -0
  304. data/sig/ibex/ir/validator/base.rbs +65 -0
  305. data/sig/ibex/ir/validator/grammar.rbs +184 -0
  306. data/sig/ibex/ir/validator/lexer.rbs +37 -0
  307. data/sig/ibex/ir/validator.rbs +16 -0
  308. data/sig/ibex/ir.rbs +38 -4
  309. data/sig/ibex/lalr/build_metrics.rbs +20 -0
  310. data/sig/ibex/lalr/builder.rbs +95 -15
  311. data/sig/ibex/lalr/conflict_search.rbs +7 -3
  312. data/sig/ibex/lalr/counterexample.rbs +5 -2
  313. data/sig/ibex/lalr/direct_lookaheads.rbs +86 -0
  314. data/sig/ibex/lalr/ielr_partition.rbs +59 -0
  315. data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
  316. data/sig/ibex/lalr.rbs +6 -0
  317. data/sig/ibex/location.rbs +67 -0
  318. data/sig/ibex/lsp/document_handlers.rbs +24 -0
  319. data/sig/ibex/lsp/document_store.rbs +94 -0
  320. data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
  321. data/sig/ibex/lsp/document_store_validation.rbs +26 -0
  322. data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
  323. data/sig/ibex/lsp/navigation_handlers.rbs +30 -0
  324. data/sig/ibex/lsp/position_codec.rbs +40 -0
  325. data/sig/ibex/lsp/protocol_error.rbs +32 -0
  326. data/sig/ibex/lsp/request_handlers.rbs +26 -0
  327. data/sig/ibex/lsp/request_support.rbs +40 -0
  328. data/sig/ibex/lsp/server.rbs +55 -0
  329. data/sig/ibex/lsp/symbol_index.rbs +78 -0
  330. data/sig/ibex/lsp/symbol_index_builder.rbs +87 -0
  331. data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
  332. data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
  333. data/sig/ibex/lsp/symbol_occurrence.rbs +25 -0
  334. data/sig/ibex/lsp/transport.rbs +35 -0
  335. data/sig/ibex/lsp/workspace.rbs +41 -0
  336. data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
  337. data/sig/ibex/lsp.rbs +7 -0
  338. data/sig/ibex/normalize/declarations.rbs +31 -0
  339. data/sig/ibex/normalize/diagnostics.rbs +9 -0
  340. data/sig/ibex/normalize/expander.rbs +20 -14
  341. data/sig/ibex/normalize/expression.rbs +10 -10
  342. data/sig/ibex/normalize/inline_expansion.rbs +120 -0
  343. data/sig/ibex/normalize/inline_validation.rbs +40 -0
  344. data/sig/ibex/normalize/lexer.rbs +37 -0
  345. data/sig/ibex/normalize/named_references.rbs +20 -0
  346. data/sig/ibex/normalize/nodes.rbs +14 -0
  347. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +32 -0
  348. data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
  349. data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
  350. data/sig/ibex/normalize/parameter_validation.rbs +44 -0
  351. data/sig/ibex/normalize/parameters.rbs +46 -0
  352. data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
  353. data/sig/ibex/normalize.rbs +123 -18
  354. data/sig/ibex/racc_migration/checker.rbs +36 -0
  355. data/sig/ibex/racc_migration/harness.rbs +16 -0
  356. data/sig/ibex/racc_migration/report.rbs +53 -0
  357. data/sig/ibex/racc_migration.rbs +8 -0
  358. data/sig/ibex/rake_task.rbs +51 -0
  359. data/sig/ibex/samples.rbs +51 -0
  360. data/sig/ibex/table_simulation/result.rbs +32 -0
  361. data/sig/ibex/table_simulation/simulator.rbs +98 -0
  362. data/sig/ibex/table_simulation/step.rbs +40 -0
  363. data/sig/ibex/table_simulation/text.rbs +14 -0
  364. data/sig/ibex/table_simulation.rbs +7 -0
  365. data/sig/ibex/tables.rbs +0 -26
  366. data/sig/ibex/watch/runner.rbs +48 -0
  367. data/sig/ibex/watch/source_snapshot.rbs +42 -0
  368. data/sig/ibex/watch.rbs +7 -0
  369. data/sig/ibex.rbs +2 -0
  370. metadata +301 -16
  371. data/.rubocop.yml +0 -43
  372. data/CHANGELOG.md +0 -30
  373. data/Rakefile +0 -25
  374. data/Steepfile +0 -10
  375. data/docs/compat-notes.md +0 -37
  376. data/docs/lexer-coverage.md +0 -14
  377. data/docs/phase10-extensions.md +0 -27
  378. data/gemfiles/Gemfile +0 -7
  379. data/gemfiles/Gemfile.lock +0 -98
  380. data/lib/ibex/frontend/grammar.y +0 -156
  381. data/lib/ibex/runtime/parser.rb +0 -360
  382. data/lib/ibex/runtime.rb +0 -8
  383. data/sig/ibex/runtime/parser.rbs +0 -167
  384. data/sig/ibex/runtime.rbs +0 -6
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Coverage
6
+ # Dependency-free structural validation for runtime-event schema version 1.
7
+ module RuntimeEventValidator
8
+ TOKEN_KEYS = %w[location state token token_id value].freeze #: Array[String]
9
+ LOCATION_KEYS = %w[column end_column end_line file line].freeze #: Array[String]
10
+ VALIDATORS = {
11
+ "start" => :valid_start?,
12
+ "shift" => :valid_shift?,
13
+ "reduce" => :valid_reduce?,
14
+ "error" => :valid_error?,
15
+ "recover" => :valid_recover?,
16
+ "discard" => :valid_discard?,
17
+ "accept" => :valid_accept?,
18
+ "reject" => :valid_reject?
19
+ }.freeze #: Hash[String, Symbol]
20
+
21
+ class << self
22
+ # @rbs (Hash[String, untyped] document) -> bool
23
+ def valid?(document)
24
+ data = document["data"]
25
+ return false unless data.is_a?(Hash) && data.keys.all?(String)
26
+
27
+ validator = VALIDATORS[document["event"]]
28
+ validator ? send(validator, data) : false
29
+ end
30
+
31
+ private
32
+
33
+ # @rbs (Hash[String, untyped] data) -> bool
34
+ def valid_start?(data)
35
+ return false unless exact_keys?(
36
+ data,
37
+ %w[driver grammar_digest initial_state production_count state_count table_format_version]
38
+ )
39
+
40
+ integer_fields = %w[initial_state table_format_version]
41
+ optional_integer_fields = %w[state_count production_count]
42
+ %w[pull push].include?(data["driver"]) &&
43
+ integer_fields.all? { |key| integer?(data[key]) } &&
44
+ optional_integer_fields.all? { |key| optional_integer?(data[key]) } &&
45
+ optional_string?(data["grammar_digest"])
46
+ end
47
+
48
+ # @rbs (Hash[String, untyped] data) -> bool
49
+ def valid_shift?(data)
50
+ exact_keys?(data, TOKEN_KEYS + %w[from_state]) &&
51
+ valid_token_fields?(data) &&
52
+ integer?(data["from_state"])
53
+ end
54
+
55
+ # @rbs (Hash[String, untyped] data) -> bool
56
+ def valid_reduce?(data)
57
+ keys = %w[goto_state lhs location post_state pre_state production_id result rhs_length]
58
+ exact_keys?(data, keys) &&
59
+ %w[goto_state lhs post_state pre_state production_id].all? { |key| integer?(data[key]) } &&
60
+ integer?(data["rhs_length"]) && data["rhs_length"] >= 0 &&
61
+ summary?(data["result"]) && location?(data["location"])
62
+ end
63
+
64
+ # @rbs (Hash[String, untyped] data, reasons: Array[String]) -> bool
65
+ def valid_token_event?(data, reasons:)
66
+ exact_keys?(data, TOKEN_KEYS + %w[reason]) &&
67
+ valid_token_fields?(data) &&
68
+ reasons.include?(data["reason"])
69
+ end
70
+
71
+ # @rbs (Hash[String, untyped] data) -> bool
72
+ def valid_error?(data)
73
+ valid_token_event?(data, reasons: %w[syntax semantic])
74
+ end
75
+
76
+ # @rbs (Hash[String, untyped] data) -> bool
77
+ def valid_discard?(data)
78
+ valid_token_event?(data, reasons: %w[recovery])
79
+ end
80
+
81
+ # @rbs (Hash[String, untyped] data) -> bool
82
+ def valid_reject?(data)
83
+ valid_token_event?(data, reasons: %w[eof_during_recovery no_recovery_state])
84
+ end
85
+
86
+ # @rbs (Hash[String, untyped] data) -> bool
87
+ def valid_recover?(data)
88
+ exact_keys?(data, TOKEN_KEYS + %w[from_state reason]) &&
89
+ valid_token_fields?(data) &&
90
+ integer?(data["from_state"]) &&
91
+ %w[syntax semantic].include?(data["reason"])
92
+ end
93
+
94
+ # @rbs (Hash[String, untyped] data) -> bool
95
+ def valid_accept?(data)
96
+ exact_keys?(data, %w[reason result state]) &&
97
+ integer?(data["state"]) &&
98
+ summary?(data["result"]) &&
99
+ %w[table semantic].include?(data["reason"])
100
+ end
101
+
102
+ # @rbs (Hash[String, untyped] data) -> bool
103
+ def valid_token_fields?(data)
104
+ integer?(data["state"]) &&
105
+ optional_integer?(data["token_id"]) &&
106
+ summary?(data["token"]) &&
107
+ summary?(data["value"]) &&
108
+ location?(data["location"])
109
+ end
110
+
111
+ # @rbs (untyped value) -> bool
112
+ def summary?(value)
113
+ case value
114
+ when nil, true, false, Integer then true
115
+ when Float then value.finite?
116
+ when String then value.length <= 256
117
+ else composite_summary?(value)
118
+ end
119
+ end
120
+
121
+ # @rbs (untyped value) -> bool
122
+ def composite_summary?(value)
123
+ return value.length <= 17 && value.all? { |entry| summary?(entry) } if value.is_a?(Array)
124
+ return false unless value.is_a?(Hash)
125
+
126
+ value.keys.all?(String) && value.length <= 17 && value.values.all? { |entry| summary?(entry) }
127
+ end
128
+
129
+ # @rbs (untyped value) -> bool
130
+ def location?(value)
131
+ return true if value.nil?
132
+ return false unless value.is_a?(Hash) && value.keys.all?(String)
133
+ return value["unavailable"].is_a?(String) if value.keys == ["unavailable"]
134
+
135
+ (value.keys - LOCATION_KEYS).empty? && value.values.all? { |entry| summary?(entry) }
136
+ end
137
+
138
+ # @rbs (Hash[String, untyped] data, Array[String] keys) -> bool
139
+ def exact_keys?(data, keys)
140
+ data.keys.sort == keys.sort
141
+ end
142
+
143
+ # @rbs (untyped value) -> bool
144
+ def integer?(value)
145
+ value.is_a?(Integer)
146
+ end
147
+
148
+ # @rbs (untyped value) -> bool
149
+ def optional_integer?(value)
150
+ value.nil? || integer?(value)
151
+ end
152
+
153
+ # @rbs (untyped value) -> bool
154
+ def optional_string?(value)
155
+ value.nil? || value.is_a?(String)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "coverage/runtime_event_validator"
5
+ require_relative "coverage/event_stream"
6
+ require_relative "coverage/report"
7
+ require_relative "coverage/collector"
8
+
9
+ module Ibex
10
+ # Deterministic state and production coverage derived from runtime event streams.
11
+ module Coverage
12
+ end
13
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "parser_v2"
4
+
5
+ module Ibex
6
+ module ErrorMessages
7
+ # Strict line-oriented parser for the ibex-messages v1 and v2 formats.
8
+ class Parser
9
+ include ParserV2
10
+
11
+ ESCAPES = { "\\" => "\\", "n" => "\n", "t" => "\t", "r" => "\r" }.freeze
12
+
13
+ # @rbs @file: String
14
+ # @rbs @lines: Array[String]
15
+
16
+ # @rbs (String source, file: String) -> void
17
+ def initialize(source, file:)
18
+ @file = file
19
+ text = source.dup.force_encoding(Encoding::UTF_8)
20
+ fail_at(1, 1, "messages file must be valid UTF-8") unless text.valid_encoding?
21
+
22
+ @lines = text.lines(chomp: true)
23
+ end
24
+
25
+ # @rbs () -> Document
26
+ def parse
27
+ version = validate_header
28
+ return parse_v2 if version == 2
29
+
30
+ parse_v1
31
+ end
32
+
33
+ private
34
+
35
+ # @rbs () -> Document
36
+ def parse_v1
37
+ entries = [] #: Array[Entry]
38
+ declarations = {} #: Hash[Integer, Integer]
39
+ index = 1
40
+ while index < @lines.length
41
+ line = @lines.fetch(index)
42
+ if ignorable?(line)
43
+ index += 1
44
+ next
45
+ end
46
+
47
+ match = line.strip.match(/\A(state|removed)\s+([0-9]+)\z/)
48
+ fail_at(index + 1, column(line), top_level_expectation) unless match
49
+ state_text = match[2] || raise(Ibex::Error, "missing state number")
50
+ state = Integer(state_text, 10)
51
+ reject_duplicate(declarations, state, index, line)
52
+ declarations[state] = index + 1
53
+ entry, index = parse_entry(index, state, match[1] == "state" ? :active : :removed)
54
+ entries << entry
55
+ end
56
+ Document.new(version: 1, entries: entries)
57
+ end
58
+
59
+ # @rbs () -> Integer
60
+ def validate_header
61
+ header = @lines.first&.delete_prefix("\uFEFF")
62
+ return 1 if header == HEADER_V1
63
+ return 2 if header == HEADER
64
+
65
+ fail_at(1, 1, "expected #{HEADER.inspect} or #{HEADER_V1.inspect}")
66
+ end
67
+
68
+ # @rbs (Hash[Integer, Integer] declarations, Integer state, Integer index, String line) -> void
69
+ def reject_duplicate(declarations, state, index, line)
70
+ return unless declarations.key?(state)
71
+
72
+ fail_at(index + 1, column(line),
73
+ "duplicate state #{state}; first declared at line #{declarations.fetch(state)}")
74
+ end
75
+
76
+ # @rbs (Integer opening_index, Integer state, :active | :removed status) -> [Entry, Integer]
77
+ def parse_entry(opening_index, state, status)
78
+ message_lines = [] #: Array[String]
79
+ index = opening_index + 1
80
+ while index < @lines.length
81
+ line = @lines.fetch(index)
82
+ return close_entry(message_lines, opening_index, index, state, status) if line.strip == "end"
83
+
84
+ read_entry_line(message_lines, line, index)
85
+ index += 1
86
+ end
87
+ label = status == :active ? "state" : "removed"
88
+ fail_at(opening_index + 1, 1, "unterminated #{label} #{state} entry")
89
+ end
90
+
91
+ # @rbs (Array[String] message_lines, Integer opening_index, Integer index, Integer state,
92
+ # :active | :removed status) -> [Entry, Integer]
93
+ def close_entry(message_lines, opening_index, index, state, status)
94
+ message = message_lines.empty? ? nil : message_lines.join("\n")
95
+ fail_at(opening_index + 1, 1, "message for state #{state} must not be empty") if message&.strip&.empty?
96
+
97
+ entry = Entry.new(state: state, status: status, message: message, line: opening_index + 1)
98
+ [entry, index + 1]
99
+ end
100
+
101
+ # @rbs (Array[String] message_lines, String line, Integer index) -> void
102
+ def read_entry_line(message_lines, line, index)
103
+ return if ignorable?(line)
104
+
105
+ unless line.start_with?("|")
106
+ fail_at(index + 1, column(line), "expected a `| ` message line, a comment, a blank line, or `end`")
107
+ end
108
+
109
+ content = line.delete_prefix("|")
110
+ content = content.delete_prefix(" ")
111
+ content_column = line.start_with?("| ") ? 3 : 2
112
+ message_lines << decode_line(content, index + 1, content_column)
113
+ end
114
+
115
+ # @rbs (String content, Integer line, Integer content_column) -> String
116
+ def decode_line(content, line, content_column)
117
+ decoded = +""
118
+ index = 0
119
+ while index < content.length
120
+ character = content[index] || raise(Ibex::Error, "missing message character")
121
+ unless character == "\\"
122
+ decoded << character
123
+ index += 1
124
+ next
125
+ end
126
+
127
+ escaped = content[index + 1]
128
+ fail_at(line, content_column + index, "trailing backslash in message line") unless escaped
129
+ replacement = ESCAPES[escaped]
130
+ fail_at(line, content_column + index, "unknown escape \\#{escaped}") unless replacement
131
+
132
+ decoded << replacement
133
+ index += 2
134
+ end
135
+ decoded
136
+ end
137
+
138
+ # @rbs (String line) -> bool
139
+ def ignorable?(line)
140
+ line.strip.empty? || line.lstrip.start_with?("#")
141
+ end
142
+
143
+ # @rbs () -> String
144
+ def top_level_expectation
145
+ "expected `state N`, `removed N`, a comment, or a blank line"
146
+ end
147
+
148
+ # @rbs (String line) -> Integer
149
+ def column(line)
150
+ (line.index(/\S/) || 0) + 1
151
+ end
152
+
153
+ # @rbs (Integer line, Integer column, String message) -> bot
154
+ def fail_at(line, column, message)
155
+ raise Ibex::Error, "#{@file}:#{line}:#{column}: #{message}"
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module ErrorMessages
5
+ # Sentence-keyed ibex-messages v2 parsing, mixed into Parser so both
6
+ # versions share UTF-8, escape, and positioned-diagnostic helpers.
7
+ module ParserV2
8
+ # @rbs!
9
+ # private def ignorable?: (String) -> bool
10
+ # private def column: (String) -> Integer
11
+ # private def fail_at: (Integer, Integer, String) -> bot
12
+ # private def decode_line: (String, Integer, Integer) -> String
13
+
14
+ private
15
+
16
+ # @rbs () -> Document
17
+ def parse_v2
18
+ entries = [] #: Array[Entry]
19
+ identifiers = {} #: Hash[String, Integer]
20
+ keys = {} #: Hash[String, Integer]
21
+ index = 1
22
+ while index < @lines.length
23
+ line = @lines.fetch(index)
24
+ if ignorable?(line) && !line.lstrip.start_with?("##")
25
+ index += 1
26
+ next
27
+ end
28
+
29
+ entry, index = parse_v2_entry(index)
30
+ record_v2_entry(entry, entries, identifiers, keys)
31
+ end
32
+ Document.new(version: 2, entries: entries)
33
+ end
34
+
35
+ # @rbs (Entry entry, Array[Entry] entries, Hash[String, Integer] identifiers,
36
+ # Hash[String, Integer] keys) -> void
37
+ def record_v2_entry(entry, entries, identifiers, keys)
38
+ error_id = entry.error_id || raise(Ibex::Error, "missing error id")
39
+ reject_v2_duplicate(identifiers, error_id, entry.line, "error id")
40
+ key = entry.sentence ? "#{entry.entry}\0#{entry.sentence.join("\0")}" : "legacy\0#{entry.state}"
41
+ reject_v2_duplicate(keys, key, entry.line, "sentence")
42
+ identifiers[error_id] = entry.line
43
+ keys[key] = entry.line
44
+ entries << entry
45
+ end
46
+
47
+ # @rbs (Integer opening_index) -> [Entry, Integer]
48
+ def parse_v2_entry(opening_index)
49
+ status, sentence, state = parse_v2_opening(opening_index)
50
+ read_v2_entry_body(opening_index, status, sentence, state)
51
+ end
52
+
53
+ # @rbs (Integer opening_index) ->
54
+ # [:active | :unreachable | :removed, Array[String]?, Integer?]
55
+ def parse_v2_opening(opening_index)
56
+ opening = @lines.fetch(opening_index)
57
+ if (legacy = opening.match(/\Alegacy-state:[ \t]*([0-9]+)\z/))
58
+ return [:removed, nil, Integer(legacy[1] || "0", 10)]
59
+ end
60
+
61
+ if (sentence = opening.match(/\A(sentence|unreachable):[ \t]*(.*)\z/))
62
+ status = sentence[1] == "unreachable" ? :unreachable : :active #: :unreachable | :active
63
+ return [status, parse_sentence(sentence[2] || "", opening_index + 1), nil]
64
+ end
65
+
66
+ fail_at(opening_index + 1, column(opening),
67
+ "expected `sentence: TOKENS`, `unreachable: TOKENS`, or `legacy-state: N`")
68
+ end
69
+
70
+ # @rbs (Integer opening_index, :active | :unreachable | :removed status, Array[String]? sentence,
71
+ # Integer? state) -> [Entry, Integer]
72
+ def read_v2_entry_body(opening_index, status, sentence, state)
73
+ error_id = nil #: String?
74
+ entry_name = nil #: String?
75
+ message_lines = [] #: Array[String]
76
+ index = opening_index + 1
77
+ while index < @lines.length
78
+ line = @lines.fetch(index)
79
+ if line.strip == "end"
80
+ return close_v2_entry(
81
+ opening_index, index, status, sentence, state, error_id, entry_name, message_lines
82
+ )
83
+ end
84
+
85
+ error_id, state, entry_name = read_v2_metadata(
86
+ line, index, error_id, state, entry_name, message_lines
87
+ )
88
+ index += 1
89
+ end
90
+ fail_at(opening_index + 1, 1, "unterminated error-sentence entry")
91
+ end
92
+
93
+ # @rbs (Integer opening_index, Integer index, :active | :unreachable | :removed status,
94
+ # Array[String]? sentence, Integer? state, String? error_id, String? entry_name,
95
+ # Array[String] message_lines) -> [Entry, Integer]
96
+ def close_v2_entry(opening_index, index, status, sentence, state, error_id, entry_name, message_lines)
97
+ fail_at(opening_index + 1, 1, "missing `## E0001` error id") unless error_id
98
+ message = message_lines.empty? ? nil : message_lines.join("\n")
99
+ fail_at(opening_index + 1, 1, "message must not be empty") if message&.strip&.empty?
100
+ entry = Entry.new(
101
+ state: state, status: status, message: message, line: opening_index + 1,
102
+ sentence: sentence, error_id: error_id, entry: entry_name
103
+ )
104
+ [entry, index + 1]
105
+ end
106
+
107
+ # @rbs (String line, Integer index, String? error_id, Integer? state, String? entry_name,
108
+ # Array[String] message_lines) -> [String?, Integer?, String?]
109
+ def read_v2_metadata(line, index, error_id, state, entry_name, message_lines)
110
+ stripped = line.strip
111
+ if (match = stripped.match(/\A##[ \t]+(E[0-9]{4,})\z/))
112
+ fail_at(index + 1, column(line), "duplicate error id") if error_id
113
+ error_id = match[1]
114
+ elsif (match = stripped.match(/\A# state:[ \t]*([0-9]+)\z/))
115
+ state = Integer(match[1] || "0", 10)
116
+ elsif (match = stripped.match(/\A# entry:[ \t]*(\S+)\z/))
117
+ entry_name = match[1]
118
+ elsif line.start_with?("|")
119
+ append_v2_message_line(message_lines, line, index)
120
+ elsif !ignorable?(line)
121
+ fail_at(index + 1, column(line), "expected metadata, a `| ` message line, a comment, or `end`")
122
+ end
123
+ [error_id, state, entry_name]
124
+ end
125
+
126
+ # @rbs (Array[String] message_lines, String line, Integer index) -> void
127
+ def append_v2_message_line(message_lines, line, index)
128
+ content = line.delete_prefix("|").delete_prefix(" ")
129
+ content_column = line.start_with?("| ") ? 3 : 2
130
+ message_lines << decode_line(content, index + 1, content_column)
131
+ end
132
+
133
+ # @rbs (String source, Integer line) -> Array[String]
134
+ def parse_sentence(source, line)
135
+ tokens = [] #: Array[String]
136
+ index = 0
137
+ loop do
138
+ index = skip_sentence_space(source, index)
139
+ break if index == source.length
140
+
141
+ token, index = read_sentence_token(source, index, line)
142
+ tokens << token
143
+ end
144
+ fail_at(line, 1, "error sentence must contain at least one token") if tokens.empty?
145
+ tokens
146
+ end
147
+
148
+ # @rbs (String source, Integer index) -> Integer
149
+ def skip_sentence_space(source, index)
150
+ index += 1 while index < source.length && source[index]&.match?(/\s/)
151
+ index
152
+ end
153
+
154
+ # @rbs (String source, Integer index, Integer line) -> [String, Integer]
155
+ def read_sentence_token(source, index, line)
156
+ start = index
157
+ quote = source[index]
158
+ if ["'", '"'].include?(quote)
159
+ index = quoted_token_end(source, index, quote || "", line)
160
+ else
161
+ index += 1 while index < source.length && !source[index]&.match?(/\s/)
162
+ end
163
+ token = source[start...index] || raise(Ibex::Error, "missing sentence token")
164
+ [token, index]
165
+ end
166
+
167
+ # @rbs (String source, Integer index, String quote, Integer line) -> Integer
168
+ def quoted_token_end(source, index, quote, line)
169
+ ending = scan_quoted_token(source, index, quote, line)
170
+ return ending if ending == source.length || source[ending]&.match?(/\s/)
171
+
172
+ fail_at(line, ending + 1, "quoted token must be followed by whitespace")
173
+ end
174
+
175
+ # @rbs (String source, Integer index, String quote, Integer line) -> Integer
176
+ def scan_quoted_token(source, index, quote, line)
177
+ index += 1
178
+ while index < source.length
179
+ character = source[index]
180
+ if character == "\\"
181
+ index += 2
182
+ elsif character == quote
183
+ return index + 1
184
+ else
185
+ index += 1
186
+ end
187
+ end
188
+ fail_at(line, source.length + 1, "unterminated quoted token")
189
+ end
190
+
191
+ # @rbs (Hash[String, Integer] declarations, String key, Integer line, String label) -> void
192
+ def reject_v2_duplicate(declarations, key, line, label)
193
+ previous = declarations[key]
194
+ fail_at(line, 1, "duplicate #{label}; first declared at line #{previous}") if previous
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module ErrorMessages
5
+ # @rbs (IR::Automaton automaton, Array[Entry] entries) -> String
6
+ def render_entries(automaton, entries)
7
+ lines = [
8
+ HEADER,
9
+ "# Generated by `ibex errors --update`; sentences and IDs are stable review keys.",
10
+ "# Edit only `| ` message lines.",
11
+ ""
12
+ ]
13
+ entries.each { |entry| append_entry(lines, automaton, entry) }
14
+ "#{lines.join("\n").rstrip}\n"
15
+ end
16
+
17
+ # @rbs (Array[String] lines, IR::Automaton automaton, Entry entry) -> void
18
+ def append_entry(lines, automaton, entry)
19
+ lines << entry_heading(entry)
20
+ lines << "## #{entry.error_id}"
21
+ lines << "# entry: #{entry.entry}" if entry.entry
22
+ lines << "# state: #{entry.state}" if entry.state
23
+ append_expected_tokens(lines, automaton, entry)
24
+ lines << "# Add one or more `| ` lines to customize this error." unless entry.message
25
+ entry.message&.split("\n", -1)&.each { |line| lines << "| #{encode_line(line)}" }
26
+ lines.push("end", "")
27
+ end
28
+
29
+ # @rbs (Entry entry) -> String
30
+ def entry_heading(entry)
31
+ return "legacy-state: #{entry.state}" unless entry.sentence
32
+
33
+ prefix = entry.status == :active ? "sentence" : "unreachable"
34
+ "#{prefix}: #{entry.sentence.join(' ')}"
35
+ end
36
+
37
+ # @rbs (Array[String] lines, IR::Automaton automaton, Entry entry) -> void
38
+ def append_expected_tokens(lines, automaton, entry)
39
+ return unless entry.status == :active && entry.state
40
+
41
+ expected = expected_tokens(automaton, automaton.states.fetch(entry.state))
42
+ lines << "# expected: #{expected.empty? ? '(none)' : expected.join(', ')}"
43
+ end
44
+
45
+ # @rbs (String line) -> String
46
+ def encode_line(line)
47
+ line.gsub("\\", "\\\\").gsub("\t", "\\t").gsub("\r", "\\r")
48
+ end
49
+
50
+ # @rbs (IR::Automaton automaton, IR::AutomatonState state) -> Array[String]
51
+ def expected_tokens(automaton, state)
52
+ ordinary_terminals(automaton).filter_map do |terminal|
53
+ action = state.actions[terminal.id] || state.default_action
54
+ terminal.display_name || terminal.name unless error_action?(action)
55
+ end
56
+ end
57
+
58
+ module_function :render_entries, :append_entry, :entry_heading, :append_expected_tokens, :encode_line,
59
+ :expected_tokens
60
+
61
+ class << self
62
+ private :render_entries, :append_entry, :entry_heading, :append_expected_tokens, :encode_line, :expected_tokens
63
+ end
64
+ end
65
+ end