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,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require_relative "../ir"
5
+
6
+ module Ibex
7
+ # @rbs!
8
+ # type migrate_ir_options = {
9
+ # paths: Array[String],
10
+ # ?to: Integer,
11
+ # ?output: String
12
+ # }
13
+
14
+ # CLI validation and structural comparison for versioned IR documents.
15
+ module CLIIRTools
16
+ # @rbs!
17
+ # private def same_file_target?: (String left, String right) -> bool
18
+ # private def atomic_write_ir: (String path, String source) -> void
19
+
20
+ private
21
+
22
+ # @rbs (Array[String] arguments) -> Integer
23
+ def run_validate_ir_command(arguments)
24
+ path = single_ir_path(arguments, "validate-ir")
25
+ value = IR::Validator.validate(File.read(path))
26
+ kind = if value.is_a?(IR::Grammar)
27
+ "grammar"
28
+ elsif value.is_a?(IR::Lexer)
29
+ "lexer"
30
+ else
31
+ "automaton"
32
+ end
33
+ @stdout.puts("valid #{kind} IR v#{value.schema_version}")
34
+ 0
35
+ end
36
+
37
+ # @rbs (Array[String] arguments) -> Integer
38
+ def run_compare_command(arguments)
39
+ raise Ibex::Error, "(cli):1:1: compare requires exactly two IR files" unless arguments.length == 2
40
+
41
+ before = IR::Validator.validate(File.read(arguments.fetch(0)))
42
+ after = IR::Validator.validate(File.read(arguments.fetch(1)))
43
+ compatible = (before.is_a?(IR::Grammar) && after.is_a?(IR::Grammar)) ||
44
+ (before.is_a?(IR::Automaton) && after.is_a?(IR::Automaton)) ||
45
+ (before.is_a?(IR::Lexer) && after.is_a?(IR::Lexer))
46
+ raise Ibex::Error, "(cli):1:1: cannot compare different IR kinds" unless compatible
47
+
48
+ @stdout.puts(JSON.pretty_generate(compare_ir(before, after)))
49
+ 0
50
+ end
51
+
52
+ # @rbs (Array[String] arguments) -> Integer
53
+ def run_migrate_ir_command(arguments)
54
+ settings = migrate_ir_options(arguments)
55
+ input = single_ir_path(settings.fetch(:paths), "migrate-ir")
56
+ output = settings[:output]
57
+ if output && same_file_target?(input, output)
58
+ raise Ibex::Error, "(cli):1:1: migrate-ir input and output paths must be distinct"
59
+ end
60
+
61
+ value = IR::Validator.validate(File.read(input))
62
+ target = settings[:to] || raise(Ibex::Error, "(cli):1:1: migrate-ir requires --to=VERSION")
63
+ if value.is_a?(IR::Lexer)
64
+ raise Ibex::Error, "(cli):1:1: lexer IR is already at its only supported schema version" unless
65
+ target == value.schema_version
66
+
67
+ source = IR::Serialize.dump(value)
68
+ output ? atomic_write_ir(output, source) : @stdout.write(source)
69
+ return 0
70
+ end
71
+ unless IR::SUPPORTED_SCHEMA_VERSIONS.include?(target)
72
+ raise Ibex::Error, "(cli):1:1: unsupported migration target schema_version #{target}"
73
+ end
74
+ if target < value.schema_version
75
+ raise Ibex::Error,
76
+ "(cli):1:1: downgrading schema_version #{value.schema_version} to #{target} is not supported"
77
+ end
78
+
79
+ source = IR::Serialize.dump(IR::Migration.to_version(value, to: target))
80
+ output ? atomic_write_ir(output, source) : @stdout.write(source)
81
+ 0
82
+ end
83
+
84
+ # @rbs (Array[String] arguments) -> migrate_ir_options
85
+ def migrate_ir_options(arguments)
86
+ settings = { paths: [] } #: migrate_ir_options
87
+ parser = OptionParser.new do |options|
88
+ options.banner = "Usage: ibex migrate-ir INPUT --to=VERSION [-o FILE]"
89
+ options.on("--to=VERSION", Integer, "target schema version") { |value| settings[:to] = value }
90
+ options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
91
+ end
92
+ settings[:paths] = parser.parse(arguments)
93
+ settings
94
+ end
95
+
96
+ # @rbs (Array[String] arguments, String command) -> String
97
+ def single_ir_path(arguments, command)
98
+ return arguments.first if arguments.length == 1
99
+
100
+ raise Ibex::Error, "(cli):1:1: #{command} requires exactly one IR file"
101
+ end
102
+
103
+ # @rbs (IR::Grammar | IR::Automaton | IR::Lexer before,
104
+ # IR::Grammar | IR::Automaton | IR::Lexer after) -> Hash[Symbol, untyped]
105
+ def compare_ir(before, after)
106
+ return compare_grammars(before, after) if before.is_a?(IR::Grammar) && after.is_a?(IR::Grammar)
107
+ if before.is_a?(IR::Automaton) && after.is_a?(IR::Automaton)
108
+ return {
109
+ kind: "automaton",
110
+ algorithm: { before: before.algorithm, after: after.algorithm },
111
+ states: numeric_change(before.states.length, after.states.length),
112
+ transitions: numeric_change(transition_count(before), transition_count(after)),
113
+ conflicts: %i[sr resolved_sr rr].to_h do |kind|
114
+ [kind, numeric_change(summary_count(before, kind), summary_count(after, kind))]
115
+ end,
116
+ grammar: compare_grammars(before.grammar, after.grammar)
117
+ }
118
+ end
119
+ return compare_lexers(before, after) if before.is_a?(IR::Lexer) && after.is_a?(IR::Lexer)
120
+
121
+ raise Ibex::Error, "(cli):1:1: unsupported IR comparison"
122
+ end
123
+
124
+ # @rbs (IR::Lexer before, IR::Lexer after) -> Hash[Symbol, untyped]
125
+ def compare_lexers(before, after)
126
+ before_rules = before.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
127
+ after_rules = after.rules.map { |rule| [rule.state, rule.kind, rule.token, rule.pattern, rule.options] }
128
+ {
129
+ kind: "lexer",
130
+ states: { added: after.states - before.states, removed: before.states - after.states },
131
+ rules: {
132
+ added: after_rules - before_rules, removed: before_rules - after_rules,
133
+ count: numeric_change(before.rules.length, after.rules.length)
134
+ },
135
+ warnings: numeric_change(before.warnings.length, after.warnings.length)
136
+ }
137
+ end
138
+
139
+ # @rbs (IR::Grammar before, IR::Grammar after) -> Hash[Symbol, untyped]
140
+ def compare_grammars(before, after)
141
+ before_symbols = before.symbols.map(&:name)
142
+ after_symbols = after.symbols.map(&:name)
143
+ before_productions = production_shapes(before)
144
+ after_productions = production_shapes(after)
145
+ {
146
+ kind: "grammar",
147
+ symbols: { added: (after_symbols - before_symbols).sort, removed: (before_symbols - after_symbols).sort },
148
+ productions: {
149
+ added: (after_productions - before_productions).sort,
150
+ removed: (before_productions - after_productions).sort,
151
+ count: numeric_change(before.productions.length, after.productions.length)
152
+ },
153
+ warnings: numeric_change(before.warnings.length, after.warnings.length)
154
+ }
155
+ end
156
+
157
+ # @rbs (IR::Grammar grammar) -> Array[String]
158
+ def production_shapes(grammar)
159
+ grammar.productions.map do |production|
160
+ lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
161
+ rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
162
+ "#{lhs} -> #{rhs.join(' ')}"
163
+ end
164
+ end
165
+
166
+ # @rbs (IR::Automaton automaton) -> Integer
167
+ def transition_count(automaton)
168
+ automaton.states.sum { |state| state.transitions.length }
169
+ end
170
+
171
+ # @rbs (IR::Automaton automaton, Symbol kind) -> Integer
172
+ def summary_count(automaton, kind)
173
+ value = automaton.conflict_summary.fetch(kind)
174
+ return value if value.is_a?(Integer)
175
+
176
+ raise Ibex::Error, "(ir):1:1: conflict count #{kind} must be an integer"
177
+ end
178
+
179
+ # @rbs (Integer before, Integer after) -> Hash[Symbol, Integer]
180
+ def numeric_change(before, after)
181
+ { before: before, after: after, delta: after - before }
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lsp"
4
+
5
+ module Ibex
6
+ # CLI boundary for the stdio-only language server.
7
+ module CLILSP
8
+ private
9
+
10
+ # @rbs (Array[String] arguments) -> Integer
11
+ def run_lsp_command(arguments)
12
+ settings = { help: false, stdio: false } #: Hash[Symbol, bool]
13
+ remaining = lsp_option_parser(settings).parse(arguments)
14
+ raise Ibex::Error, "(cli):1:1: ibex lsp does not accept file arguments" unless remaining.empty?
15
+
16
+ if settings.fetch(:help)
17
+ @stdout.puts(lsp_option_parser(settings))
18
+ return 0
19
+ end
20
+
21
+ LSP::Server.new(stdin: @stdin, stdout: @stdout, stderr: @stderr).run
22
+ end
23
+
24
+ # @rbs (Hash[Symbol, bool] settings) -> OptionParser
25
+ def lsp_option_parser(settings)
26
+ OptionParser.new do |options|
27
+ options.banner = "Usage: ibex lsp [--stdio]"
28
+ options.on("--stdio", "use Content-Length framed JSON-RPC on standard IO") { settings[:stdio] = true }
29
+ options.on("--help", "show help") { settings[:help] = true }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,14 +3,24 @@
3
3
  module Ibex
4
4
  # Files, warnings, and progress emitted by CLI pipeline stages.
5
5
  module CLIOutputs
6
+ # @rbs!
7
+ # private def register_artifact: (Symbol, String, String, ?mode: Integer?, ?status: bool) -> Artifact
8
+
6
9
  WARNING_MESSAGES = {
7
10
  undeclared_terminal: ->(warning) { "undeclared terminal #{warning[:symbol]}" },
8
11
  unused_terminal: ->(warning) { "unused terminal #{warning[:symbol]}" },
12
+ unused_precedence: ->(warning) { "unused precedence #{warning[:symbol]}" },
13
+ unreachable_terminal: ->(warning) { "declared terminal #{warning[:symbol]} is unreachable" },
9
14
  unreachable_nonterminal: ->(warning) { "unreachable nonterminal #{warning[:symbol]}" },
10
15
  duplicate_production: lambda do |warning|
11
16
  "duplicate production #{warning[:production]} (first defined as #{warning[:original]})"
12
17
  end,
13
- empty_language: ->(warning) { "start symbol #{warning[:symbol]} derives no terminal sentence" }
18
+ implicit_empty: ->(_warning) { "implicit empty alternative; write %empty to document intent" },
19
+ empty_language: ->(warning) { "start symbol #{warning[:symbol]} derives no terminal sentence" },
20
+ lexer_redos: lambda do |warning|
21
+ subject = warning[:symbol] ? " for #{warning[:symbol]}" : ""
22
+ "lexer pattern#{subject} may exhibit excessive backtracking"
23
+ end
14
24
  }.freeze #: Hash[Symbol, ^(IR::grammar_warning) -> String]
15
25
 
16
26
  private
@@ -55,34 +65,107 @@ module Ibex
55
65
 
56
66
  # @rbs (IR::Automaton automaton, String input_path) -> void
57
67
  def report_conflicts(automaton, input_path)
58
- summary = automaton.conflict_summary
68
+ messages = conflict_messages(automaton.conflict_summary, input_path)
69
+ if @options[:warnings]&.include?(:error) && messages.any?
70
+ raise Ibex::Error, messages.map { |message| "#{message}; conflict treated as error" }.join("\n")
71
+ end
72
+
73
+ messages.each { |message| @stderr.puts(message) }
74
+ end
75
+
76
+ # @rbs (IR::conflict_summary summary, String input_path) -> Array[String]
77
+ def conflict_messages(summary, input_path)
78
+ messages = [] #: Array[String]
59
79
  unless summary[:expectation_met]
60
- @stderr.puts("#{input_path}:1:1: #{summary[:sr]} shift/reduce conflicts; expected #{summary[:expected_sr]}")
80
+ messages << "#{input_path}:1:1: #{summary[:sr]} shift/reduce conflicts; expected #{summary[:expected_sr]}"
81
+ end
82
+ if summary.key?(:rr_expectation_met)
83
+ unless summary[:rr_expectation_met]
84
+ messages << "#{input_path}:1:1: #{summary[:rr]} reduce/reduce conflicts; expected #{summary[:expected_rr]}"
85
+ end
86
+ elsif summary[:rr].positive?
87
+ messages << "#{input_path}:1:1: #{summary[:rr]} reduce/reduce conflicts"
61
88
  end
62
- @stderr.puts("#{input_path}:1:1: #{summary[:rr]} reduce/reduce conflicts") if summary[:rr].positive?
89
+ messages
90
+ end
91
+
92
+ # @rbs (IR::Automaton automaton, String input_path) -> void
93
+ def suggest_ielr(automaton, input_path)
94
+ return unless @options[:suggest_ielr]
95
+ return unless automaton.algorithm == "lalr1"
96
+ return unless [nil, :lalr].include?(@options[:algorithm])
97
+
98
+ summary = automaton.conflict_summary
99
+ return if summary[:expectation_met] && summary.fetch(:rr_expectation_met, summary[:rr].zero?)
100
+
101
+ ielr = LALR::Builder.new(
102
+ automaton.grammar, algorithm: :ielr, entry_isolation: @options[:entry_isolation] == true
103
+ ).build
104
+ removed_sr = [summary[:sr] - ielr.conflict_summary[:sr], 0].max
105
+ removed_rr = [summary[:rr] - ielr.conflict_summary[:rr], 0].max
106
+ avoided = [] #: Array[String]
107
+ avoided << conflict_count(removed_sr, "shift/reduce") if removed_sr.positive?
108
+ avoided << conflict_count(removed_rr, "reduce/reduce") if removed_rr.positive?
109
+ return if avoided.empty?
110
+
111
+ @stderr.puts("#{input_path}:1:1: note: --algorithm=ielr avoids #{avoided.join(' and ')}; " \
112
+ "consider --algorithm=ielr")
113
+ end
114
+
115
+ # @rbs (Integer count, String kind) -> String
116
+ def conflict_count(count, kind)
117
+ "#{count} #{kind} conflict#{'s' unless count == 1}"
63
118
  end
64
119
 
65
120
  # @rbs (IR::Automaton automaton, String input_path) -> void
66
121
  def write_report(automaton, input_path)
122
+ require_relative "../codegen/report"
67
123
  path = @options[:log_file] || default_output_path(input_path, ".output")
68
124
  report = Codegen::Report.render(
69
125
  automaton,
70
126
  max_tokens: @options[:counterexample_max_tokens],
71
127
  max_configurations: @options[:counterexample_max_configurations]
72
128
  )
73
- File.write(path, report)
74
- report_status("wrote #{path}")
129
+ register_artifact(:report, path, report, status: true)
75
130
  end
76
131
 
77
132
  # @rbs (String input_path, String extension) -> String
78
133
  def default_output_path(input_path, extension)
79
- replaced = input_path.sub(/\.[^.]+\z/, extension)
80
- replaced == input_path ? "#{input_path}#{extension}" : replaced
134
+ directory = File.dirname(input_path)
135
+ basename = File.basename(input_path)
136
+ replaced = basename.sub(/\.[^.]+\z/, extension)
137
+ replaced = "#{basename}#{extension}" if replaced == basename
138
+ directory == "." ? replaced : File.join(directory, replaced)
81
139
  end
82
140
 
83
141
  # @rbs (String message) -> void
84
142
  def report_status(message)
85
143
  @stderr.puts("ibex: #{message}") if @options[:status]
86
144
  end
145
+
146
+ # @rbs (String path, String source) -> void
147
+ def atomic_write_ir(path, source)
148
+ require "tempfile"
149
+
150
+ target_path = File.symlink?(path) ? File.realpath(path) : path
151
+ directory = File.dirname(File.expand_path(target_path))
152
+ basename = File.basename(target_path)
153
+ Tempfile.create([".#{basename}.", ".tmp"], directory) do |temporary|
154
+ temporary.binmode
155
+ temporary.write(source)
156
+ temporary.flush
157
+ temporary.fsync
158
+ temporary.chmod(ir_file_mode(target_path))
159
+ temporary.close
160
+ File.rename(temporary.path, target_path)
161
+ end
162
+ end
163
+
164
+ # @rbs (String path) -> Integer
165
+ def ir_file_mode(path)
166
+ return File.stat(path).mode & 0o777 if File.exist?(path)
167
+
168
+ 0o666 & ~File.umask
169
+ end
87
170
  end
88
171
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "json"
5
+ require "optparse"
6
+ require_relative "../racc_migration"
7
+
8
+ module Ibex
9
+ # Static racc migration checks and differential harness generation.
10
+ module CLIRaccMigration
11
+ # @rbs!
12
+ # type migration_check_settings = {
13
+ # format: String,
14
+ # ?help: bool
15
+ # }
16
+ # type migration_harness_settings = {
17
+ # ?output: String,
18
+ # ?help: bool
19
+ # }
20
+ #
21
+ # private def input_path: (Array[String]) -> String
22
+ # private def atomic_write_ir: (String path, String source) -> void
23
+ # private def same_file_target?: (String left, String right) -> bool
24
+
25
+ private
26
+
27
+ # @rbs (Array[String] arguments) -> Integer
28
+ def run_migrate_check_command(arguments)
29
+ settings = { format: "text" } #: migration_check_settings
30
+ parser = migrate_check_options(settings)
31
+ paths = parser.parse(arguments)
32
+ if settings[:help]
33
+ @stdout.puts(parser)
34
+ return 0
35
+ end
36
+
37
+ path = input_path(paths)
38
+ report = RaccMigration::Checker.new.check(File.binread(path), file: path)
39
+ @stdout.write(settings.fetch(:format) == "json" ? "#{report.to_json}\n" : report.to_text)
40
+ report.compatible? ? 0 : 1
41
+ end
42
+
43
+ # @rbs (Array[String] arguments) -> Integer
44
+ def run_migrate_harness_command(arguments)
45
+ settings = {} #: migration_harness_settings
46
+ parser = migrate_harness_options(settings)
47
+ paths = parser.parse(arguments)
48
+ if settings[:help]
49
+ @stdout.puts(parser)
50
+ return 0
51
+ end
52
+
53
+ path = input_path(paths)
54
+ report = RaccMigration::Checker.new.check(File.binread(path), file: path)
55
+ unless report.compatible? && report.class_name
56
+ raise Ibex::Error, "(migration):1:1: grammar must pass migrate-check before harness generation"
57
+ end
58
+
59
+ source = RaccMigration::Harness.generate(report.class_name)
60
+ output = settings[:output]
61
+ if output && same_file_target?(path, output)
62
+ raise Ibex::Error, "(migration):1:1: grammar and harness output paths must be distinct"
63
+ end
64
+
65
+ output ? atomic_write_ir(output, source) : @stdout.write(source)
66
+ 0
67
+ end
68
+
69
+ # @rbs (migration_check_settings settings) -> OptionParser
70
+ def migrate_check_options(settings)
71
+ OptionParser.new do |options|
72
+ options.banner = "Usage: ibex migrate-check [--format=text|json] grammarfile"
73
+ options.on("--format=FORMAT", %w[text json], "text or json") { |value| settings[:format] = value }
74
+ options.on("--help", "show help") { settings[:help] = true }
75
+ end
76
+ end
77
+
78
+ # @rbs (migration_harness_settings settings) -> OptionParser
79
+ def migrate_harness_options(settings)
80
+ OptionParser.new do |options|
81
+ options.banner = "Usage: ibex migrate-harness [-o FILE] grammarfile"
82
+ options.on("-o FILE", "--output=FILE", "write the harness atomically") { |value| settings[:output] = value }
83
+ options.on("--help", "show help") { settings[:help] = true }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "optparse"
5
+ require_relative "../samples"
6
+
7
+ module Ibex
8
+ # CLI entry point for bounded grammar sentence generation.
9
+ module CLISamples
10
+ # @rbs!
11
+ # private def print_help: (OptionParser) -> Integer
12
+ # private def input_path: (Array[String]) -> String
13
+ # private def handle_grammar_warnings: (IR::Grammar, String) -> void
14
+ # private def warning_categories: (String) -> Array[Symbol]
15
+ # private def normalize_grammar_path: (String) -> IR::Grammar
16
+
17
+ private
18
+
19
+ # @rbs (Array[String] arguments) -> Integer
20
+ def run_samples_command(arguments)
21
+ parser = samples_option_parser
22
+ remaining = parser.parse(arguments)
23
+ return print_help(parser) if @options[:help]
24
+
25
+ path = input_path(remaining)
26
+ grammar = grammar_for_samples(path)
27
+ handle_grammar_warnings(grammar, path)
28
+ generator = Samples.new(
29
+ grammar,
30
+ seed: @options.fetch(:sample_seed, 0),
31
+ max_tokens: @options.fetch(:sample_max_tokens, 32),
32
+ max_depth: @options.fetch(:sample_max_depth, 16),
33
+ max_expansions: @options.fetch(:sample_max_expansions, Samples::DEFAULT_MAX_EXPANSIONS)
34
+ )
35
+ generator.generate(count: @options.fetch(:sample_count, 5)).each do |sample|
36
+ @stdout.puts(JSON.generate(sample))
37
+ end
38
+ 0
39
+ end
40
+
41
+ # @rbs () -> OptionParser
42
+ def samples_option_parser
43
+ OptionParser.new do |options|
44
+ options.banner = "Usage: ibex samples [options] grammarfile"
45
+ options.on("--count=N", Integer, "number of samples (default 5)") do |value|
46
+ @options[:sample_count] = positive_sample_option!("count", value)
47
+ end
48
+ options.on("--seed=N", Integer, "deterministic random seed (default 0)") do |value|
49
+ @options[:sample_seed] = value
50
+ end
51
+ options.on("--max-tokens=N", Integer, "maximum tokens per sample (default 32)") do |value|
52
+ @options[:sample_max_tokens] = positive_sample_option!("max-tokens", value)
53
+ end
54
+ options.on("--max-depth=N", Integer, "random expansion depth (default 16)") do |value|
55
+ @options[:sample_max_depth] = positive_sample_option!("max-depth", value)
56
+ end
57
+ options.on("--max-expansions=N", Integer, "total expansion steps (default 100000)") do |value|
58
+ @options[:sample_max_expansions] = positive_sample_option!("max-expansions", value)
59
+ end
60
+ options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "read versioned IR JSON") do |value|
61
+ @options[:from] = value
62
+ end
63
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
64
+ options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
65
+ @options[:warnings] = warning_categories(value)
66
+ end
67
+ options.on("--help", "show help") { @options[:help] = true }
68
+ end
69
+ end
70
+
71
+ # @rbs (String path) -> IR::Grammar
72
+ def grammar_for_samples(path)
73
+ return normalize_grammar_path(path) unless @options[:from]
74
+
75
+ value = IR::Validator.validate(File.read(path))
76
+ expected = @options[:from] == "grammar-ir" ? IR::Grammar : IR::Automaton
77
+ raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input" unless value.is_a?(expected)
78
+
79
+ return value if value.is_a?(IR::Grammar)
80
+ return value.grammar if value.is_a?(IR::Automaton)
81
+
82
+ raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input"
83
+ end
84
+
85
+ # @rbs (String name, Integer value) -> Integer
86
+ def positive_sample_option!(name, value)
87
+ return value if value.positive?
88
+
89
+ raise OptionParser::InvalidArgument, "--#{name} must be positive"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "../watch"
5
+ require_relative "../generation_transaction"
6
+
7
+ module Ibex
8
+ # Long-running CLI generation over stable source snapshots.
9
+ module CLIWatch
10
+ # @rbs!
11
+ # private def process_grammar: (String) -> Integer
12
+ # private def generation_artifacts: () -> ArtifactSet
13
+ # private def report_status: (String) -> void
14
+
15
+ # Rendered watch generation awaiting its stability-guarded publication.
16
+ class Build
17
+ attr_reader :artifacts #: ArtifactSet
18
+ attr_reader :statuses #: Array[String]
19
+ attr_reader :source_paths #: Array[String]
20
+ attr_reader :attempted_paths #: Array[String]
21
+ attr_reader :source_records #: Array[GenerationInput]
22
+
23
+ # rubocop:disable Layout/LineLength
24
+ # @rbs (artifacts: ArtifactSet, statuses: Array[String], source_paths: Array[String], attempted_paths: Array[String], source_records: Array[GenerationInput]) -> void
25
+ def initialize(artifacts:, statuses:, source_paths:, attempted_paths:, source_records:)
26
+ @artifacts = artifacts
27
+ @statuses = statuses.freeze
28
+ @source_paths = source_paths.freeze
29
+ @attempted_paths = attempted_paths.freeze
30
+ @source_records = source_records.freeze
31
+ freeze
32
+ end
33
+ # rubocop:enable Layout/LineLength
34
+ end
35
+
36
+ private
37
+
38
+ # @rbs (String path) -> Integer
39
+ def run_watch(path)
40
+ raise Ibex::Error, "(cli):1:1: --watch requires a grammar file, not stdin" if path == "-"
41
+
42
+ paths = [path]
43
+ paths << @options[:messages] if @options[:messages]
44
+ Watch::Runner.new(
45
+ paths: paths,
46
+ build: -> { prepare_watch_generation(path) },
47
+ publish: ->(build, snapshot, continue) { publish_watch_generation(build, snapshot, continue) },
48
+ failure_paths: -> { watch_failure_paths(path) },
49
+ stderr: @stderr,
50
+ clock: @watch_clock,
51
+ sleeper: @watch_sleeper,
52
+ iteration_hook: @watch_iteration_hook
53
+ ).run
54
+ end
55
+
56
+ # @rbs (String path) -> Build
57
+ def prepare_watch_generation(path)
58
+ @defer_generation_publication = true
59
+ status = process_grammar(path)
60
+ raise Ibex::Error, "(watch):1:1: generation returned status #{status}" unless status.zero?
61
+
62
+ Build.new(
63
+ artifacts: generation_artifacts,
64
+ statuses: @generation_statuses.dup,
65
+ source_paths: @generation_sources.dup,
66
+ attempted_paths: @last_resolver&.attempted_paths || [],
67
+ source_records: @generation_inputs.dup
68
+ )
69
+ ensure
70
+ @defer_generation_publication = false
71
+ end
72
+
73
+ # @rbs (Build build, Watch::SourceSnapshot snapshot, ^() -> bool continue) -> void
74
+ def publish_watch_generation(build, snapshot, continue)
75
+ stable = lambda do
76
+ continue.call && build.source_records.all?(&:current?) &&
77
+ Watch::SourceSnapshot.new(snapshot.paths) == snapshot
78
+ end
79
+ GenerationTransaction.new(
80
+ build.artifacts,
81
+ warning: ->(message) { @stderr.puts("ibex: warning: #{message}") },
82
+ stability_check: stable,
83
+ source_records: build.source_records,
84
+ lock_sleeper: @watch_sleeper
85
+ ).commit
86
+ build.statuses.each { |message| report_status(message) }
87
+ end
88
+
89
+ # @rbs (String path) -> Array[String]
90
+ def watch_failure_paths(path)
91
+ paths = [path]
92
+ paths << @options[:messages] if @options[:messages]
93
+ paths.concat(@last_resolver.attempted_paths) if @last_resolver
94
+ unless @generation_artifacts.nil?
95
+ generation_artifacts.each do |artifact|
96
+ paths << artifact.path
97
+ paths << File.dirname(artifact.path)
98
+ end
99
+ end
100
+ paths.compact
101
+ end
102
+ end
103
+ end