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,386 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "optparse"
5
+ require_relative "../frontend"
6
+
7
+ module Ibex
8
+ # CLI coordination for deterministic, semantics-preserving grammar formatting.
9
+ # rubocop:disable Metrics/ModuleLength -- transaction lifecycle stays cohesive and auditable.
10
+ module CLIFormatting
11
+ # @rbs!
12
+ # type formatting_settings = {
13
+ # paths: Array[String],
14
+ # mode: Symbol,
15
+ # check: bool,
16
+ # write: bool,
17
+ # ?stdin_filename: String,
18
+ # ?help: bool
19
+ # }
20
+ # type formatting_result = {
21
+ # path: String,
22
+ # label: String,
23
+ # source: String,
24
+ # formatted: String
25
+ # }
26
+ # type formatting_target = {
27
+ # path: String,
28
+ # label: String,
29
+ # target: String,
30
+ # formatted: String,
31
+ # mode: Integer,
32
+ # directory: String,
33
+ # basename: String,
34
+ # changed: bool,
35
+ # installed: bool,
36
+ # ?stage: String,
37
+ # ?backup: String
38
+ # }
39
+ # type formatting_cleanup_result = {
40
+ # errors: Array[String],
41
+ # remaining: Array[String]
42
+ # }
43
+
44
+ private
45
+
46
+ # @rbs (Array[String] arguments) -> Integer
47
+ def run_format_command(arguments)
48
+ settings = formatting_options(arguments)
49
+ if settings[:help]
50
+ @stdout.puts(formatting_option_parser(settings))
51
+ return 0
52
+ end
53
+
54
+ validate_formatting_settings!(settings)
55
+ results, failed = prepare_formatting_results(settings)
56
+ if settings.fetch(:check)
57
+ check_status = check_formatting_results(results)
58
+ return 1 if failed
59
+
60
+ return check_status
61
+ end
62
+ return 1 if failed
63
+ return write_formatting_results(results) if settings.fetch(:write)
64
+
65
+ @stdout.write(results.fetch(0).fetch(:formatted))
66
+ 0
67
+ end
68
+
69
+ # @rbs (Array[String] arguments) -> formatting_settings
70
+ def formatting_options(arguments)
71
+ settings = { paths: [], mode: :default, check: false, write: false } #: formatting_settings
72
+ settings[:paths] = formatting_option_parser(settings).parse(arguments)
73
+ settings
74
+ end
75
+
76
+ # @rbs (formatting_settings settings) -> OptionParser
77
+ def formatting_option_parser(settings)
78
+ OptionParser.new do |options|
79
+ options.banner = "Usage: ibex fmt [--check | --write] [options] grammar.y [...]"
80
+ options.on("--check", "report files that need formatting") { settings[:check] = true }
81
+ options.on("--write", "atomically format files in place") { settings[:write] = true }
82
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
83
+ options.on("--stdin-filename=FILE", "diagnostic filename for standard input") do |value|
84
+ settings[:stdin_filename] = value
85
+ end
86
+ options.on("--help", "show help") { settings[:help] = true }
87
+ end
88
+ end
89
+
90
+ # @rbs (formatting_settings settings) -> void
91
+ def validate_formatting_settings!(settings)
92
+ paths = settings.fetch(:paths)
93
+ raise Ibex::Error, "(cli):1:1: fmt requires at least one grammar file" if paths.empty?
94
+
95
+ validate_formatting_operation!(settings, paths)
96
+ validate_formatting_stdin!(settings, paths)
97
+ end
98
+
99
+ # @rbs (formatting_settings settings, Array[String] paths) -> void
100
+ def validate_formatting_operation!(settings, paths)
101
+ if settings.fetch(:check) && settings.fetch(:write)
102
+ raise Ibex::Error, "(cli):1:1: fmt --check and --write cannot be combined"
103
+ end
104
+ return if settings.fetch(:check) || settings.fetch(:write) || paths.length == 1
105
+
106
+ raise Ibex::Error, "(cli):1:1: fmt writes to stdout only for exactly one input"
107
+ end
108
+
109
+ # @rbs (formatting_settings settings, Array[String] paths) -> void
110
+ def validate_formatting_stdin!(settings, paths)
111
+ raise Ibex::Error, "(cli):1:1: fmt standard input may be specified only once" if paths.count("-") > 1
112
+
113
+ if paths.include?("-") && (settings.fetch(:check) || settings.fetch(:write))
114
+ raise Ibex::Error, "(cli):1:1: fmt --check and --write require file inputs"
115
+ end
116
+ if settings[:stdin_filename] && formatting_control_bytes?(settings.fetch(:stdin_filename))
117
+ raise Ibex::Error, "(cli):1:1: --stdin-filename must not contain control characters"
118
+ end
119
+ return unless settings[:stdin_filename] && paths != ["-"]
120
+
121
+ raise Ibex::Error, "(cli):1:1: --stdin-filename requires fmt -"
122
+ end
123
+
124
+ # @rbs (formatting_settings settings) -> [Array[formatting_result], bool]
125
+ def prepare_formatting_results(settings)
126
+ failed = false
127
+ results = [] #: Array[formatting_result]
128
+ settings.fetch(:paths).each do |path|
129
+ source = path == "-" ? @stdin.read : File.binread(path)
130
+ label = path == "-" ? settings[:stdin_filename] || "(stdin)" : formatting_label(path)
131
+ formatted = Frontend::Formatter.format(source, file: label, mode: settings.fetch(:mode))
132
+ results << { path: path, label: label, source: source, formatted: formatted }
133
+ rescue Ibex::Error, SystemCallError => e
134
+ @stderr.puts(formatting_label(e.message))
135
+ failed = true
136
+ end
137
+ [results, failed]
138
+ end
139
+
140
+ # @rbs (Array[formatting_result] results) -> Integer
141
+ def check_formatting_results(results)
142
+ changed = results.reject { |result| result.fetch(:source) == result.fetch(:formatted) }
143
+ changed.each { |result| @stderr.puts("#{result.fetch(:label)}: needs formatting") }
144
+ changed.empty? ? 0 : 1
145
+ end
146
+
147
+ # @rbs (Array[formatting_result] results) -> Integer
148
+ def write_formatting_results(results)
149
+ targets = formatting_targets(results)
150
+ validate_unique_formatting_targets!(targets)
151
+ targets = targets.select { |target| target.fetch(:changed) }
152
+ return 0 if targets.empty?
153
+
154
+ transactionally_write_formatted(targets)
155
+ 0
156
+ end
157
+
158
+ # @rbs (Array[formatting_result] results) -> Array[formatting_target]
159
+ def formatting_targets(results)
160
+ results.map do |result|
161
+ target = File.realpath(result.fetch(:path))
162
+ {
163
+ path: result.fetch(:path), label: result.fetch(:label), target: target,
164
+ formatted: result.fetch(:formatted), mode: File.stat(target).mode & 0o7777,
165
+ directory: File.dirname(target), basename: File.basename(target),
166
+ changed: result.fetch(:source) != result.fetch(:formatted), installed: false
167
+ }
168
+ end
169
+ rescue SystemCallError => e
170
+ raise Ibex::Error, formatting_label(e.message)
171
+ end
172
+
173
+ # @rbs (Array[formatting_target] targets) -> void
174
+ def validate_unique_formatting_targets!(targets)
175
+ collision = duplicate_formatting_target_pair(targets)
176
+ return unless collision
177
+
178
+ labels = collision.map { |target| target.fetch(:label) }
179
+ raise Ibex::Error, "(cli):1:1: fmt inputs resolve to the same target: #{labels.join(', ')}"
180
+ end
181
+
182
+ # @rbs (Array[formatting_target] targets) -> [formatting_target, formatting_target]?
183
+ def duplicate_formatting_target_pair(targets)
184
+ targets.each_with_index do |left, index|
185
+ targets.drop(index + 1).each do |right|
186
+ same_path = left.fetch(:target) == right.fetch(:target)
187
+ return [left, right] if same_path || File.identical?(left.fetch(:target), right.fetch(:target))
188
+ end
189
+ end
190
+ nil
191
+ end
192
+
193
+ # @rbs (Array[formatting_target] targets) -> void
194
+ def transactionally_write_formatted(targets)
195
+ begin
196
+ stage_formatting_targets(targets)
197
+ backup_formatting_targets(targets)
198
+ sync_formatting_directories!(targets)
199
+ install_formatting_targets(targets)
200
+ sync_formatting_directories!(targets)
201
+ rescue StandardError => e
202
+ rollback_errors = rollback_formatting_targets(targets)
203
+ cleanup = cleanup_formatting_artifacts(targets, preserve_installed_backups: true)
204
+ sync_errors = sync_formatting_directories_best_effort(targets)
205
+ raise formatting_transaction_error(e, rollback_errors, cleanup, sync_errors)
206
+ end
207
+
208
+ cleanup = cleanup_formatting_artifacts(targets, preserve_installed_backups: false)
209
+ sync_errors = sync_formatting_directories_best_effort(targets)
210
+ report_formatting_cleanup_warning(cleanup, sync_errors)
211
+ end
212
+
213
+ # @rbs (Array[formatting_target] targets) -> void
214
+ def stage_formatting_targets(targets)
215
+ targets.each do |target|
216
+ temporary = Tempfile.create(
217
+ [".ibex-fmt-", ".stage"], target.fetch(:directory)
218
+ )
219
+ target[:stage] = temporary.path.dup
220
+ temporary.binmode
221
+ temporary.write(target.fetch(:formatted))
222
+ temporary.flush
223
+ temporary.fsync
224
+ temporary.chmod(target.fetch(:mode))
225
+ temporary.fsync
226
+ temporary.close
227
+ rescue StandardError
228
+ begin
229
+ temporary&.close
230
+ rescue StandardError
231
+ nil
232
+ end
233
+ raise
234
+ end
235
+ end
236
+
237
+ # @rbs (Array[formatting_target] targets) -> void
238
+ def backup_formatting_targets(targets)
239
+ targets.each do |target|
240
+ backup = "#{target.fetch(:stage)}.backup"
241
+ target[:backup] = backup
242
+ File.link(target.fetch(:target), backup)
243
+ end
244
+ end
245
+
246
+ # @rbs (Array[formatting_target] targets) -> void
247
+ def install_formatting_targets(targets)
248
+ targets.each do |target|
249
+ File.rename(target.fetch(:stage), target.fetch(:target))
250
+ target[:installed] = true
251
+ end
252
+ end
253
+
254
+ # @rbs (Array[formatting_target] targets) -> Array[String]
255
+ def rollback_formatting_targets(targets)
256
+ errors = [] #: Array[String]
257
+ targets.reverse_each do |target|
258
+ next unless target.fetch(:installed)
259
+
260
+ File.rename(target.fetch(:backup), target.fetch(:target))
261
+ target[:installed] = false
262
+ rescue StandardError => e
263
+ errors << "#{target.fetch(:label)}: #{formatting_label(e.message)}"
264
+ end
265
+ errors
266
+ end
267
+
268
+ # @rbs (Array[formatting_target] targets, preserve_installed_backups: bool) ->
269
+ # formatting_cleanup_result
270
+ def cleanup_formatting_artifacts(targets, preserve_installed_backups:)
271
+ errors = [] #: Array[String]
272
+ remaining = [] #: Array[String]
273
+ targets.each do |target|
274
+ artifacts = [target[:stage]] #: Array[String?]
275
+ backup = target[:backup]
276
+ if preserve_installed_backups && target.fetch(:installed) && backup
277
+ remaining << backup
278
+ else
279
+ artifacts << backup
280
+ end
281
+ artifacts.compact.each do |path|
282
+ error = remove_formatting_artifact(path)
283
+ next unless error
284
+
285
+ errors << error
286
+ remaining << path
287
+ end
288
+ end
289
+ { errors: errors, remaining: remaining }
290
+ end
291
+
292
+ # @rbs (String path) -> String?
293
+ def remove_formatting_artifact(path)
294
+ attempts = 0
295
+ begin
296
+ attempts += 1
297
+ File.unlink(path)
298
+ nil
299
+ rescue Errno::ENOENT
300
+ nil
301
+ rescue StandardError => e
302
+ retry if attempts < 2
303
+
304
+ "#{formatting_label(path)}: #{formatting_label(e.message)}"
305
+ end
306
+ end
307
+
308
+ # @rbs (StandardError error, Array[String] rollback_errors,
309
+ # formatting_cleanup_result cleanup, Array[String] sync_errors) -> Ibex::Error
310
+ def formatting_transaction_error(error, rollback_errors, cleanup, sync_errors)
311
+ details = ["fmt write failed: #{formatting_label(error.message)}"] #: Array[String]
312
+ details << "rollback failed: #{rollback_errors.join('; ')}" unless rollback_errors.empty?
313
+ details << "cleanup failed: #{cleanup.fetch(:errors).join('; ')}" unless cleanup.fetch(:errors).empty?
314
+ unless cleanup.fetch(:remaining).empty?
315
+ paths = cleanup.fetch(:remaining).map { |path| formatting_label(path) }
316
+ details << "preserved artifacts: #{paths.join(', ')}"
317
+ end
318
+ details << "rollback directory sync failed: #{sync_errors.join('; ')}" unless sync_errors.empty?
319
+ Ibex::Error.new(details.join("; "))
320
+ end
321
+
322
+ # @rbs (formatting_cleanup_result cleanup, Array[String] sync_errors) -> void
323
+ def report_formatting_cleanup_warning(cleanup, sync_errors)
324
+ details = cleanup.fetch(:errors).dup
325
+ details.concat(sync_errors)
326
+ unless cleanup.fetch(:remaining).empty?
327
+ paths = cleanup.fetch(:remaining).map { |path| formatting_label(path) }
328
+ details << "remaining artifacts: #{paths.join(', ')}"
329
+ end
330
+ return if details.empty?
331
+
332
+ @stderr.puts("fmt cleanup warning (formatted targets committed): #{details.join('; ')}")
333
+ end
334
+
335
+ # @rbs (Array[formatting_target] targets) -> void
336
+ def sync_formatting_directories!(targets)
337
+ errors = sync_formatting_directories(targets)
338
+ return if errors.empty?
339
+
340
+ raise Ibex::Error, "directory sync failed: #{errors.join('; ')}"
341
+ end
342
+
343
+ # @rbs (Array[formatting_target] targets) -> Array[String]
344
+ def sync_formatting_directories_best_effort(targets)
345
+ sync_formatting_directories(targets)
346
+ end
347
+
348
+ # @rbs (Array[formatting_target] targets) -> Array[String]
349
+ def sync_formatting_directories(targets)
350
+ errors = [] #: Array[String]
351
+ targets.map { |target| target.fetch(:directory) }.uniq.each do |directory|
352
+ sync_formatting_directory(directory)
353
+ rescue SystemCallError, IOError => e
354
+ errors << "#{formatting_label(directory)}: #{formatting_label(e.message)}"
355
+ end
356
+ errors
357
+ end
358
+
359
+ # @rbs (String directory) -> void
360
+ def sync_formatting_directory(directory)
361
+ File.open(directory, File::RDONLY, &:fsync)
362
+ rescue Errno::EINVAL, Errno::ENOTSUP, Errno::EBADF
363
+ nil
364
+ end
365
+
366
+ # @rbs (String value) -> String
367
+ def formatting_label(value)
368
+ escaped = String.new(encoding: Encoding::BINARY)
369
+ value.b.each_byte do |byte|
370
+ escaped << (control_byte?(byte) ? format("\\x%02X", byte) : byte)
371
+ end
372
+ escaped.force_encoding(value.encoding)
373
+ end
374
+
375
+ # @rbs (String value) -> bool
376
+ def formatting_control_bytes?(value)
377
+ value.b.each_byte.any? { |byte| control_byte?(byte) }
378
+ end
379
+
380
+ # @rbs (Integer byte) -> bool
381
+ def control_byte?(byte)
382
+ byte < 0x20 || byte == 0x7f
383
+ end
384
+ end
385
+ # rubocop:enable Metrics/ModuleLength
386
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "../artifact_set"
5
+ require_relative "../generation_input"
6
+ require_relative "../generation_transaction"
7
+
8
+ module Ibex
9
+ # Collects, verifies, and transactionally publishes CLI generation outputs.
10
+ module CLIGenerationArtifacts
11
+ # @rbs!
12
+ # private def report_status: (String) -> void
13
+ # private def verify_file: (String, String, String) -> void
14
+ # private def default_output_path: (String, String) -> String
15
+
16
+ private
17
+
18
+ # @rbs () -> void
19
+ def begin_artifact_generation
20
+ @generation_artifacts = ArtifactSet.new
21
+ @generation_statuses = [] #: Array[String]
22
+ @generation_sources = [] #: Array[String]
23
+ @generation_inputs = [] #: Array[GenerationInput]
24
+ end
25
+
26
+ # @rbs (Symbol kind, String path, String content, ?mode: Integer?, ?status: bool) -> Artifact
27
+ def register_artifact(kind, path, content, mode: nil, status: false)
28
+ artifact = generation_artifacts.add(kind: kind, path: path, content: content, mode: mode)
29
+ @generation_statuses << "wrote #{path}" if status
30
+ artifact
31
+ end
32
+
33
+ # @rbs (Array[String] source_paths) -> Integer
34
+ def finish_artifact_generation(_source_paths)
35
+ add_generation_manifest if @options[:manifest]
36
+ return 0 if @defer_generation_publication
37
+
38
+ ensure_generation_inputs_stable!
39
+ if @options[:verify_output]
40
+ verify_artifact_generation
41
+ ensure_generation_inputs_stable!
42
+ elsif !generation_artifacts.empty?
43
+ GenerationTransaction.new(
44
+ generation_artifacts,
45
+ warning: ->(message) { @stderr.puts("ibex: warning: #{message}") },
46
+ stability_check: -> { generation_inputs_stable? },
47
+ source_records: @generation_inputs
48
+ ).commit
49
+ @generation_statuses.each { |message| report_status(message) }
50
+ end
51
+ 0
52
+ end
53
+
54
+ # @rbs () -> void
55
+ def add_generation_manifest
56
+ require_relative "../generation_manifest"
57
+
58
+ path = manifest_output_path
59
+ source = GenerationManifest.render(
60
+ generation_artifacts,
61
+ source_records: @generation_inputs,
62
+ options: generation_manifest_options
63
+ )
64
+ register_artifact(:manifest, path, source, status: true)
65
+ end
66
+
67
+ # @rbs () -> void
68
+ def verify_artifact_generation
69
+ generation_artifacts.each do |artifact|
70
+ verify_file(artifact.path, artifact.content, artifact_label(artifact.kind))
71
+ end
72
+ parser = generation_artifacts.find { |artifact| artifact.kind == :parser }
73
+ report_status("verified #{parser.path}") if parser
74
+ end
75
+
76
+ # @rbs (Symbol kind) -> String
77
+ def artifact_label(kind)
78
+ {
79
+ parser: "parser",
80
+ rbs: "RBS signature",
81
+ action_source: "action source",
82
+ manifest: "generation manifest"
83
+ }.fetch(kind, kind.to_s.tr("_", " "))
84
+ end
85
+
86
+ # @rbs () -> ArtifactSet
87
+ def generation_artifacts
88
+ @generation_artifacts || raise(Ibex::Error, "(generation):1:1: artifact collection is not active")
89
+ end
90
+
91
+ # @rbs () -> bool
92
+ def generation_inputs_stable?
93
+ @generation_inputs.all?(&:current?)
94
+ end
95
+
96
+ # @rbs () -> void
97
+ def ensure_generation_inputs_stable!
98
+ return if generation_inputs_stable?
99
+
100
+ raise GenerationTransaction::SourceChanged, "(generation):1:1: source changed while rendering outputs"
101
+ end
102
+
103
+ # @rbs (String path, String source) -> GenerationInput
104
+ def record_generation_input(path, source)
105
+ input = GenerationInput.new(path, source)
106
+ @generation_inputs.reject! { |entry| entry.path == input.path }
107
+ @generation_inputs << input
108
+ @generation_sources = @generation_inputs.map(&:path)
109
+ input
110
+ end
111
+
112
+ # @rbs () -> String
113
+ def manifest_output_path
114
+ configured = @options[:manifest]
115
+ return configured if configured.is_a?(String) && !configured.empty?
116
+
117
+ parser = generation_artifacts.find { |artifact| artifact.kind == :parser }
118
+ raise Ibex::Error, "(generation):1:1: generation manifest requires a parser artifact" unless parser
119
+
120
+ default_output_path(parser.path, ".ibex.json")
121
+ end
122
+
123
+ # @rbs () -> Hash[String, untyped]
124
+ def generation_manifest_options
125
+ keys = %i[
126
+ action_source algorithm counterexample_max_configurations counterexample_max_tokens debug
127
+ embedded emit entry_isolation executable frozen line_convert line_convert_all messages mode omit_actions rbs
128
+ superclass table
129
+ ]
130
+ options = {} #: Hash[String, untyped]
131
+ keys.each do |key|
132
+ value = @options[key]
133
+ options[key.to_s] = value unless value.nil?
134
+ end
135
+ options
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # Optional example-keyed error-message support for parser generation.
6
+ module CLIGenerationErrorMessages
7
+ # @rbs!
8
+ # private def record_generation_input: (String, String) -> GenerationInput
9
+
10
+ private
11
+
12
+ # @rbs (OptionParser options) -> void
13
+ def add_error_messages_generation_option(options)
14
+ options.on("--messages=FILE", "embed example-keyed syntax error messages") do |value|
15
+ @options[:messages] = value
16
+ end
17
+ end
18
+
19
+ # @rbs (IR::Automaton automaton) -> Hash[Integer, untyped]
20
+ def configured_error_messages(automaton)
21
+ path = @options[:messages]
22
+ return {} unless path
23
+
24
+ require_relative "../error_messages"
25
+ source = File.binread(path)
26
+ record_generation_input(path, source)
27
+ document = ErrorMessages.parse(source, file: path)
28
+ ErrorMessages.records_for(document, automaton, file: path)
29
+ end
30
+
31
+ # @rbs () -> void
32
+ def validate_messages_options
33
+ return unless @options[:messages]
34
+ raise Ibex::Error, "(cli):1:1: --messages is available only with --emit=ruby" unless @options[:emit] == "ruby"
35
+
36
+ raise Ibex::Error, "(cli):1:1: --messages cannot be combined with --check-only" if @options[:check_only]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require_relative "../grammar_tests"
5
+
6
+ module Ibex
7
+ # `%test` command parsing and human-readable result reporting.
8
+ module CLIGrammarTests
9
+ # @rbs!
10
+ # type grammar_test_settings = {
11
+ # mode: Symbol,
12
+ # algorithm: Symbol,
13
+ # entry_isolation: bool,
14
+ # timeout: Integer,
15
+ # coverage: Integer?,
16
+ # ?help: bool
17
+ # }
18
+ # private def print_help: (OptionParser) -> Integer
19
+ # private def resolve_grammar_path: (String) -> Frontend::Resolution
20
+ # private def handle_grammar_warnings: (IR::Grammar, String) -> void
21
+ # private def build_automaton: (IR::Grammar, String) -> IR::Automaton
22
+
23
+ private
24
+
25
+ # @rbs (Array[String] arguments) -> Integer
26
+ def run_grammar_tests_command(arguments)
27
+ settings = {
28
+ mode: :default, algorithm: :lalr, entry_isolation: false, timeout: GrammarTests::DEFAULT_TIMEOUT, coverage: nil
29
+ } #: grammar_test_settings
30
+ options = grammar_test_option_parser(settings)
31
+ remaining = options.parse(arguments)
32
+ return print_help(options) if settings[:help]
33
+ unless settings[:coverage].nil? || settings[:coverage].between?(1, 100)
34
+ raise OptionParser::InvalidArgument, "coverage must be between 1 and 100"
35
+ end
36
+
37
+ path = single_grammar_test_path(remaining, options)
38
+ @options[:mode] = settings[:mode]
39
+ @options[:algorithm] = settings[:algorithm]
40
+ @options[:entry_isolation] = settings[:entry_isolation]
41
+ resolution = resolve_grammar_path(path)
42
+ grammar = Normalizer.new(resolution, mode: settings[:mode]).normalize
43
+ handle_grammar_warnings(grammar, path)
44
+ automaton = build_automaton(grammar, path)
45
+ runner = GrammarTests::Runner.new(automaton, timeout: settings[:timeout])
46
+ results = runner.run
47
+ failures = render_grammar_test_results(results)
48
+ coverage = settings[:coverage]
49
+ return failures unless coverage
50
+
51
+ render_grammar_test_coverage(runner.production_coverage(results), coverage, failures)
52
+ end
53
+
54
+ # @rbs (grammar_test_settings settings) -> OptionParser
55
+ def grammar_test_option_parser(settings)
56
+ OptionParser.new do |options|
57
+ options.banner = "Usage: ibex test [options] grammarfile"
58
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
59
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
60
+ settings[:algorithm] = value.to_sym
61
+ end
62
+ options.on("--entry-isolation", "build independent state sets for each start symbol") do
63
+ settings[:entry_isolation] = true
64
+ end
65
+ options.on("--timeout=SECONDS", Integer, "whole-suite timeout (default: 10)") do |value|
66
+ raise OptionParser::InvalidArgument, "timeout must be positive" unless value.positive?
67
+
68
+ settings[:timeout] = value
69
+ end
70
+ options.on("--coverage=PERCENT", Integer, "require production coverage (1..100)") do |value|
71
+ settings[:coverage] = value
72
+ end
73
+ options.on("-h", "--help", "show this help") { settings[:help] = true }
74
+ end
75
+ end
76
+
77
+ # @rbs (Array[String] remaining, OptionParser options) -> String
78
+ def single_grammar_test_path(remaining, options)
79
+ return remaining.fetch(0) if remaining.one?
80
+
81
+ raise OptionParser::MissingArgument, options.banner if remaining.empty?
82
+
83
+ raise OptionParser::InvalidArgument, "test accepts exactly one grammarfile"
84
+ end
85
+
86
+ # @rbs (Array[GrammarTests::Result] results) -> Integer
87
+ def render_grammar_test_results(results)
88
+ failures = 0
89
+ results.each_with_index do |result, index|
90
+ label = "#{result.expectation} #{result.location[:file]}:#{result.location[:line]}"
91
+ if result.passed?
92
+ @stdout.puts("ok #{index + 1} - #{label}")
93
+ else
94
+ failures += 1
95
+ @stdout.puts("not ok #{index + 1} - #{label} (#{grammar_test_failure(result)})")
96
+ end
97
+ end
98
+ @stdout.puts("#{results.length} tests, #{failures} failures")
99
+ failures.zero? ? 0 : 1
100
+ end
101
+
102
+ # @rbs (GrammarTests::ProductionCoverage coverage, Integer minimum, Integer failures) -> Integer
103
+ def render_grammar_test_coverage(coverage, minimum, failures)
104
+ @stdout.puts(
105
+ format(
106
+ "production coverage: %<covered>d/%<total>d (%<percentage>.1f%%), required %<minimum>d%%",
107
+ covered: coverage.covered_ids.length, total: coverage.production_count,
108
+ percentage: coverage.percentage, minimum: minimum
109
+ )
110
+ )
111
+ @stdout.puts("missing production ids: #{coverage.missing_ids.join(', ')}") unless coverage.complete?
112
+ failures.zero? && coverage.meets?(minimum) ? 0 : 1
113
+ end
114
+
115
+ # @rbs (GrammarTests::Result result) -> String
116
+ def grammar_test_failure(result)
117
+ return "parser #{result.actual}ed the input" unless result.actual == :error
118
+
119
+ "#{result.error_class}: #{result.error_message}"
120
+ end
121
+ end
122
+ end