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
data/lib/ibex/cli.rb CHANGED
@@ -1,39 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require "optparse"
4
- require_relative "../ibex"
5
+ require_relative "version"
6
+ require_relative "error"
7
+ require_relative "artifact_set"
8
+ require_relative "generation_input"
9
+ require_relative "generation_transaction"
10
+ require_relative "frontend/generation"
11
+ require_relative "ir/grammar_ir"
12
+ require_relative "ir/lexer_ir"
13
+ require_relative "ir/serialize"
14
+ require_relative "ir/automaton_ir"
15
+ require_relative "normalize"
16
+ require_relative "analysis"
17
+ require_relative "lalr/conflict"
18
+ require_relative "lalr/on_error_reductions"
19
+ require_relative "lalr/default_reductions"
20
+ require_relative "lalr/build_metrics"
21
+ require_relative "lalr/direct_lookaheads"
22
+ require_relative "lalr/ielr_partition"
23
+ require_relative "lalr/builder"
24
+ require_relative "codegen/ruby"
5
25
  require_relative "cli/counterexample_options"
26
+ require_relative "cli/generation_error_messages"
27
+ require_relative "cli/generation_artifacts"
6
28
  require_relative "cli/outputs"
7
29
 
8
30
  module Ibex
31
+ CLI_FEATURE_ROOT = File.expand_path("cli", __dir__ || raise("CLI source directory is unavailable")) #: String
32
+ autoload :CLIAmbiguity, File.join(CLI_FEATURE_ROOT, "ambiguity")
33
+ autoload :CLICoverage, File.join(CLI_FEATURE_ROOT, "coverage")
34
+ autoload :CLIDebug, File.join(CLI_FEATURE_ROOT, "debug")
35
+ autoload :CLIDiagnostics, File.join(CLI_FEATURE_ROOT, "diagnostics")
36
+ autoload :CLIDocumentation, File.join(CLI_FEATURE_ROOT, "documentation")
37
+ autoload :CLIErrorMessages, File.join(CLI_FEATURE_ROOT, "error_messages")
38
+ autoload :CLIExplain, File.join(CLI_FEATURE_ROOT, "explain")
39
+ autoload :CLIFormatting, File.join(CLI_FEATURE_ROOT, "formatting")
40
+ autoload :CLIGrammarTests, File.join(CLI_FEATURE_ROOT, "grammar_tests")
41
+ autoload :CLIIRTools, File.join(CLI_FEATURE_ROOT, "ir_tools")
42
+ autoload :CLILSP, File.join(CLI_FEATURE_ROOT, "lsp")
43
+ autoload :CLIRaccMigration, File.join(CLI_FEATURE_ROOT, "racc_migration")
44
+ autoload :CLISamples, File.join(CLI_FEATURE_ROOT, "samples")
45
+ autoload :CLIWatch, File.join(CLI_FEATURE_ROOT, "watch")
46
+
9
47
  # @rbs!
10
48
  # interface _CLIOutput
11
49
  # def puts: (*untyped) -> untyped
12
50
  # def write: (String) -> untyped
13
51
  # end
52
+ # interface _CLIInput
53
+ # def read: () -> String
54
+ # def gets: () -> String?
55
+ # end
14
56
  # type cli_options = {
15
57
  # emit: String,
16
58
  # mode: Symbol,
17
59
  # table: Symbol,
60
+ # ?cst_trivia: Symbol,
18
61
  # line_convert: bool,
19
62
  # ?line_convert_all: bool,
20
63
  # counterexample_max_tokens: Integer,
21
64
  # counterexample_max_configurations: Integer,
22
65
  # ?from: String,
23
66
  # ?algorithm: Symbol,
67
+ # ?suggest_ielr: bool,
68
+ # ?entry_isolation: bool,
24
69
  # ?warnings: Array[Symbol],
25
70
  # ?output: String,
26
71
  # ?embedded: bool,
27
72
  # ?debug: bool,
28
73
  # ?verbose: bool,
29
74
  # ?rbs: String | true,
75
+ # ?action_source: String | true,
76
+ # ?manifest: String | true,
77
+ # ?watch: bool,
30
78
  # ?dot: String,
79
+ # ?mermaid: String,
31
80
  # ?html: String,
81
+ # ?railroad: String,
82
+ # ?messages: String,
83
+ # ?messages_update: String | true,
84
+ # ?messages_algorithm_explicit: bool,
85
+ # ?sample_count: Integer,
86
+ # ?sample_seed: Integer,
87
+ # ?sample_max_tokens: Integer,
88
+ # ?sample_max_depth: Integer,
89
+ # ?sample_max_expansions: Integer,
32
90
  # ?log_file: String,
33
91
  # ?executable: String,
34
92
  # ?frozen: bool,
35
93
  # ?omit_actions: bool,
36
94
  # ?superclass: String,
95
+ # ?verify_output: bool,
37
96
  # ?check_only: bool,
38
97
  # ?status: bool,
39
98
  # ?profile: bool,
@@ -41,74 +100,179 @@ module Ibex
41
100
  # ?version: bool,
42
101
  # ?runtime_version: bool,
43
102
  # ?copyright: bool,
44
- # ?help: bool
103
+ # ?help: bool,
104
+ # ?explain_state: Integer,
105
+ # ?explain_token: String,
106
+ # ?explain_format: String,
107
+ # ?check_ambiguity: bool,
108
+ # ?check_format: String
45
109
  # }
46
110
 
47
111
  # Command-line pipeline coordinator.
48
112
  # rubocop:disable Metrics/ClassLength -- inline type contracts add lines without adding runtime responsibilities.
49
113
  class CLI
114
+ SUBCOMMAND_HANDLERS = {
115
+ "check" => %i[CLIAmbiguity run_check_command],
116
+ "diagnose" => %i[CLIDiagnostics run_diagnose_command],
117
+ "coverage" => %i[CLICoverage run_coverage_command],
118
+ "debug" => %i[CLIDebug run_debug_command],
119
+ "doc" => %i[CLIDocumentation run_documentation_command],
120
+ "errors" => %i[CLIErrorMessages run_error_messages_command],
121
+ "explain" => %i[CLIExplain run_explain_command],
122
+ "fmt" => %i[CLIFormatting run_format_command],
123
+ "test" => %i[CLIGrammarTests run_grammar_tests_command],
124
+ "lsp" => %i[CLILSP run_lsp_command],
125
+ "migrate-check" => %i[CLIRaccMigration run_migrate_check_command],
126
+ "migrate-harness" => %i[CLIRaccMigration run_migrate_harness_command],
127
+ "samples" => %i[CLISamples run_samples_command],
128
+ "validate-ir" => %i[CLIIRTools run_validate_ir_command],
129
+ "compare" => %i[CLIIRTools run_compare_command],
130
+ "migrate-ir" => %i[CLIIRTools run_migrate_ir_command]
131
+ }.freeze #: Hash[String, [Symbol, Symbol]]
132
+
50
133
  include CLICounterexampleOptions
134
+ include CLIGenerationErrorMessages
135
+ include CLIGenerationArtifacts
51
136
  include CLIOutputs
52
137
 
53
138
  # @rbs @stdout: _CLIOutput
54
139
  # @rbs @stderr: _CLIOutput
55
140
  # @rbs @options: cli_options
56
141
 
57
- # @rbs (Array[String] arguments, ?stdout: _CLIOutput, ?stderr: _CLIOutput) -> Integer
58
- def self.start(arguments, stdout: $stdout, stderr: $stderr)
59
- new(stdout: stdout, stderr: stderr).run(arguments)
142
+ # rubocop:disable Layout/LineLength
143
+ # @rbs (Array[String] arguments, ?stdin: _CLIInput, ?stdout: _CLIOutput, ?stderr: _CLIOutput, ?watch_clock: (^() -> Float)?, ?watch_sleeper: (^(Float) -> void)?, ?watch_iteration_hook: (^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil))?) -> Integer
144
+ def self.start(arguments, stdin: $stdin, stdout: $stdout, stderr: $stderr, watch_clock: nil, watch_sleeper: nil,
145
+ watch_iteration_hook: nil)
146
+ new(
147
+ stdin: stdin, stdout: stdout, stderr: stderr, watch_clock: watch_clock,
148
+ watch_sleeper: watch_sleeper, watch_iteration_hook: watch_iteration_hook
149
+ ).run(arguments)
60
150
  end
61
151
 
62
- # @rbs (stdout: _CLIOutput, stderr: _CLIOutput) -> void
63
- def initialize(stdout:, stderr:)
152
+ # @rbs (?stdin: _CLIInput, stdout: _CLIOutput, stderr: _CLIOutput, ?watch_clock: (^() -> Float)?, ?watch_sleeper: (^(Float) -> void)?, ?watch_iteration_hook: (^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil))?) -> void
153
+ def initialize(stdout:, stderr:, stdin: $stdin, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil)
154
+ @stdin = stdin
64
155
  @stdout = stdout
65
156
  @stderr = stderr
66
- @options = { emit: "ruby", mode: :racc, table: :compact, line_convert: true }
157
+ @watch_clock = watch_clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
158
+ @watch_sleeper = watch_sleeper || ->(seconds) { sleep(seconds) }
159
+ @watch_iteration_hook = watch_iteration_hook || ->(_event, _iteration, _paths) {}
160
+ @options = { emit: "ruby", mode: :default, table: :compact, line_convert: true }
67
161
  .merge(CLICounterexampleOptions::DEFAULTS)
68
162
  end
163
+ # rubocop:enable Layout/LineLength
69
164
 
70
165
  # @rbs (Array[String] arguments) -> Integer
71
166
  def run(arguments)
167
+ subcommand = dispatch_subcommand(arguments)
168
+ return subcommand unless subcommand.nil?
169
+
72
170
  parser = option_parser
73
171
  remaining = parser.parse(arguments)
172
+ validate_watch_information_options
74
173
  information = informational_result(parser)
75
174
  return information unless information.nil?
76
175
 
77
- process_grammar(input_path(remaining))
78
- rescue OptionParser::ParseError, Ibex::Error, Errno::ENOENT => e
176
+ validate_messages_options
177
+ validate_generation_options
178
+ path = input_path(remaining)
179
+ validate_generation_paths!(path)
180
+ if @options[:watch]
181
+ run_watch_feature(path)
182
+ else
183
+ process_grammar(path)
184
+ end
185
+ rescue OptionParser::ParseError, Ibex::Error, SystemCallError, SystemStackError => e
79
186
  @stderr.puts(e.message)
80
187
  1
81
188
  end
82
189
 
83
190
  private
84
191
 
192
+ # @rbs (Array[String] arguments) -> Integer?
193
+ def dispatch_subcommand(arguments)
194
+ definition = SUBCOMMAND_HANDLERS[arguments.first]
195
+ return unless definition
196
+
197
+ feature, handler = definition
198
+ activate_cli_feature(feature)
199
+ send(handler, arguments.drop(1))
200
+ end
201
+
202
+ # @rbs (Symbol feature) -> void
203
+ def activate_cli_feature(feature)
204
+ extension = Ibex.const_get(feature)
205
+ extend extension unless singleton_class.ancestors.include?(extension)
206
+ end
207
+
208
+ # @rbs (String path) -> Integer
209
+ def run_watch_feature(path)
210
+ activate_cli_feature(:CLIWatch)
211
+ send(:run_watch, path)
212
+ end
213
+
85
214
  # @rbs () -> OptionParser
86
215
  def option_parser
87
216
  OptionParser.new do |options|
88
217
  options.banner = "Usage: ibex [options] grammarfile"
89
218
  add_pipeline_options(options)
90
219
  add_output_options(options)
220
+ add_error_messages_generation_option(options)
91
221
  add_compatibility_options(options)
92
222
  add_information_options(options)
223
+ options.separator("")
224
+ options.separator("Subcommands:")
225
+ options.separator(" check --ambiguity search for ambiguity within explicit budgets")
226
+ options.separator(" coverage collect, merge, or check runtime coverage")
227
+ options.separator(" debug AUTOMATON [TOKEN] simulate validated Automaton IR tables")
228
+ options.separator(" diagnose collect frontend diagnostics")
229
+ options.separator(" doc render grammar documentation")
230
+ options.separator(" errors --list|--update list or update example-keyed syntax error messages")
231
+ options.separator(" explain explain selected parser conflicts")
232
+ options.separator(" fmt format grammar source")
233
+ options.separator(" test run grammar-declared source examples")
234
+ options.separator(" lsp run the language server over stdio")
235
+ options.separator(" migrate-check statically check a racc grammar migration")
236
+ options.separator(" migrate-harness generate a differential subprocess harness")
237
+ options.separator(" samples generate bounded terminal sentences")
238
+ options.separator(" validate-ir FILE validate a versioned IR document")
239
+ options.separator(" compare BEFORE AFTER compare two versioned IR documents")
240
+ options.separator(" migrate-ir INPUT --to=2 migrate a versioned IR document")
93
241
  end
94
242
  end
95
243
 
96
244
  # @rbs (OptionParser options) -> void
97
245
  def add_pipeline_options(options)
98
- options.on("--emit=FORMAT", "ast, grammar-ir, automaton-ir, or ruby") { |value| @options[:emit] = value }
246
+ options.on("--emit=FORMAT", "ast, sets, lexer-ir, grammar-ir, automaton-ir, or ruby") do |value|
247
+ @options[:emit] = value
248
+ end
99
249
  options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
100
250
  @options[:from] = value
101
251
  end
102
- options.on("--mode=MODE", %w[racc extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
252
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
103
253
  options.on("--table=FORMAT", %w[plain compact], "parser table format") do |value|
104
254
  @options[:table] = value.to_sym
105
255
  end
106
- options.on("--algorithm=NAME", %w[slr lalr lr1], "parser construction algorithm") do |value|
256
+ options.on(
257
+ "--cst-trivia=POLICY",
258
+ %w[leading balanced drop attach],
259
+ "leading, balanced, drop, or attach (alias for leading)"
260
+ ) do |value|
261
+ @options[:cst_trivia] = value.to_sym
262
+ end
263
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
107
264
  @options[:algorithm] = value.to_sym
108
265
  end
266
+ options.on("--suggest-ielr", "check whether IELR removes unexpected LALR conflicts") do
267
+ @options[:suggest_ielr] = true
268
+ end
269
+ options.on("--entry-isolation", "build independent state sets for each start symbol") do
270
+ @options[:entry_isolation] = true
271
+ end
109
272
  options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
110
273
  @options[:warnings] = warning_categories(value)
111
274
  end
275
+ options.on("--watch", "regenerate file outputs when grammar sources change") { @options[:watch] = true }
112
276
  end
113
277
 
114
278
  # @rbs (OptionParser options) -> void
@@ -119,11 +283,16 @@ module Ibex
119
283
  options.on("-g", "obsolete alias for --debug") { @options[:debug] = true }
120
284
  options.on("-v", "--verbose", "write an automaton report") { @options[:verbose] = true }
121
285
  add_counterexample_options(options)
122
- options.on("--rbs[=FILE]", "write an RBS signature (defaults beside parser)") do |value|
123
- @options[:rbs] = value || true
286
+ add_signature_output_options(options)
287
+ options.on("--manifest[=FILE]", "write a generation manifest (defaults beside parser)") do |value|
288
+ @options[:manifest] = value || true
124
289
  end
125
290
  options.on("--dot=FILE", "write Graphviz DOT") { |value| @options[:dot] = value }
291
+ options.on("--mermaid=FILE", "write a Mermaid flowchart") { |value| @options[:mermaid] = value }
126
292
  options.on("--html=FILE", "write a self-contained HTML report") { |value| @options[:html] = value }
293
+ options.on("--railroad=FILE", "write a self-contained SVG railroad diagram") do |value|
294
+ @options[:railroad] = value
295
+ end
127
296
  options.on("-O", "--log-file=FILE", "automaton report path") do |value|
128
297
  @options[:verbose] = true
129
298
  @options[:log_file] = value
@@ -133,6 +302,16 @@ module Ibex
133
302
  end
134
303
  end
135
304
 
305
+ # @rbs (OptionParser options) -> void
306
+ def add_signature_output_options(options)
307
+ options.on("--rbs[=FILE]", "write an RBS signature (defaults beside parser)") do |value|
308
+ @options[:rbs] = value || true
309
+ end
310
+ options.on("--action-source[=FILE]", "write static-check-only action Ruby (defaults beside parser)") do |value|
311
+ @options[:action_source] = value || true
312
+ end
313
+ end
314
+
136
315
  # @rbs (OptionParser options) -> void
137
316
  def add_compatibility_options(options)
138
317
  options.on("-F", "--frozen", "emit frozen string literals") { @options[:frozen] = true }
@@ -146,6 +325,7 @@ module Ibex
146
325
  end
147
326
  options.on("-a", "--no-omit-actions", "generate implicit action methods") { @options[:omit_actions] = false }
148
327
  options.on("--superclass=CLASS", "override parser superclass") { |value| @options[:superclass] = value }
328
+ options.on("--check", "verify generated parser content without rewriting") { @options[:verify_output] = true }
149
329
  options.on("-C", "--check-only", "check grammar and exit") { @options[:check_only] = true }
150
330
  options.on("-S", "--output-status", "show pipeline status") { @options[:status] = true }
151
331
  options.on("-P", "accept the compatibility profiling flag") { @options[:profile] = true }
@@ -177,34 +357,206 @@ module Ibex
177
357
  path
178
358
  end
179
359
 
360
+ # @rbs () -> void
361
+ def validate_generation_options
362
+ if @options[:entry_isolation] && @options[:from] == "automaton-ir"
363
+ raise Ibex::Error, "(cli):1:1: --entry-isolation cannot be combined with --from=automaton-ir"
364
+ end
365
+
366
+ validate_watch_generation_options if @options[:watch]
367
+ validate_manifest_generation_options
368
+ validate_action_source_generation_options
369
+ validate_verification_generation_options
370
+ end
371
+
372
+ # @rbs () -> void
373
+ def validate_manifest_generation_options
374
+ return unless @options[:manifest]
375
+
376
+ raise Ibex::Error, "(cli):1:1: --manifest requires --emit=ruby" unless @options[:emit] == "ruby"
377
+ raise Ibex::Error, "(cli):1:1: --manifest and --check-only cannot be combined" if @options[:check_only]
378
+ end
379
+
380
+ # @rbs () -> void
381
+ def validate_action_source_generation_options
382
+ return unless @options[:action_source]
383
+
384
+ raise Ibex::Error, "(cli):1:1: --action-source requires --emit=ruby" unless @options[:emit] == "ruby"
385
+ raise Ibex::Error, "(cli):1:1: --action-source and --check-only cannot be combined" if @options[:check_only]
386
+ end
387
+
388
+ # @rbs () -> void
389
+ def validate_verification_generation_options
390
+ return unless @options[:verify_output]
391
+
392
+ raise Ibex::Error, "(cli):1:1: --check requires --emit=ruby" unless @options[:emit] == "ruby"
393
+ raise Ibex::Error, "(cli):1:1: --check and --check-only cannot be combined" if @options[:check_only]
394
+ end
395
+
396
+ # @rbs () -> void
397
+ def validate_watch_information_options
398
+ return unless @options[:watch]
399
+ return unless %i[version runtime_version copyright help].any? { |key| @options[key] }
400
+
401
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with information options"
402
+ end
403
+
404
+ # @rbs () -> void
405
+ def validate_watch_generation_options
406
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --from" if @options[:from]
407
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check" if @options[:verify_output]
408
+ raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check-only" if @options[:check_only]
409
+ return if @options[:emit] == "ruby"
410
+
411
+ raise Ibex::Error, "(cli):1:1: --watch requires --emit=ruby with file outputs"
412
+ end
413
+
414
+ # @rbs (String input_path, ?source_paths: Array[String]) -> void
415
+ def validate_generation_paths!(input_path, source_paths: [input_path])
416
+ outputs = generation_paths(input_path).except(:input, :messages)
417
+ paths = outputs.filter_map do |kind, path|
418
+ [kind, path] if path
419
+ end #: Array[[Symbol, String]]
420
+ source_entries = source_paths.map.with_index do |path, index|
421
+ [index.zero? ? :input : :"include_#{index}", path]
422
+ end
423
+ source_entries << [:messages, @options[:messages]] if @options[:messages]
424
+ paths.concat(source_entries)
425
+ collision = paths.combination(2).find do |pair|
426
+ left = pair.fetch(0)
427
+ right = pair.fetch(1)
428
+ same_file_target?(left.fetch(1), right.fetch(1))
429
+ end
430
+ return unless collision
431
+
432
+ labels = collision.map { |kind, path| "#{kind}=#{path}" }
433
+ raise Ibex::Error, "(cli):1:1: paths must be distinct: #{labels.join(', ')}"
434
+ end
435
+
436
+ # @rbs (String left, String right) -> bool
437
+ def same_file_target?(left, right)
438
+ expanded_left = File.expand_path(left)
439
+ expanded_right = File.expand_path(right)
440
+ return true if expanded_left == expanded_right
441
+
442
+ return true if File.exist?(expanded_left) && File.exist?(expanded_right) &&
443
+ File.identical?(expanded_left, expanded_right)
444
+
445
+ portable_target_key(expanded_left) == portable_target_key(expanded_right)
446
+ rescue SystemCallError
447
+ expanded_left == expanded_right
448
+ end
449
+
450
+ # @rbs (String path) -> [String, String]
451
+ def portable_target_key(path)
452
+ canonical = canonical_target_path(path)
453
+ basename = File.basename(canonical)
454
+ folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
455
+ basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
456
+ else
457
+ basename.downcase
458
+ end
459
+ [File.dirname(canonical), folded]
460
+ end
461
+
462
+ # @rbs (String path, ?Hash[String, bool] seen) -> String
463
+ def canonical_target_path(path, seen = {})
464
+ suffix = [] #: Array[String]
465
+ cursor = File.expand_path(path)
466
+ until File.exist?(cursor) || File.symlink?(cursor)
467
+ parent = File.dirname(cursor)
468
+ return path if parent == cursor
469
+
470
+ suffix.unshift(File.basename(cursor))
471
+ cursor = parent
472
+ end
473
+ if File.symlink?(cursor)
474
+ raise Errno::ELOOP, cursor if seen[cursor]
475
+
476
+ seen[cursor] = true
477
+ target = File.expand_path(File.readlink(cursor), File.dirname(cursor))
478
+ return File.join(canonical_target_path(target, seen), *suffix)
479
+ end
480
+
481
+ File.join(File.realpath(cursor), *suffix)
482
+ end
483
+
484
+ # @rbs (String input_path) -> Hash[Symbol, String?]
485
+ def generation_paths(input_path)
486
+ paths = { input: input_path, messages: @options[:messages] } #: Hash[Symbol, String?]
487
+ if @options[:emit] == "ruby" && !@options[:check_only]
488
+ output = @options[:output] || default_output_path(input_path, ".rb")
489
+ paths[:parser] = output
490
+ paths[:rbs] = rbs_output_path(output) if @options[:rbs]
491
+ paths[:action_source] = action_source_output_path(output) if @options[:action_source]
492
+ paths[:manifest] = manifest_output_path_for(output) if @options[:manifest]
493
+ end
494
+ unless @options[:verify_output]
495
+ paths.merge!(dot: @options[:dot], mermaid: @options[:mermaid], html: @options[:html],
496
+ railroad: @options[:railroad])
497
+ paths[:report] = @options[:log_file] || default_output_path(input_path, ".output") if @options[:verbose]
498
+ end
499
+ paths
500
+ end
501
+
180
502
  # @rbs (String path) -> Integer
181
503
  def process_grammar(path)
182
504
  return process_ir(path) if @options[:from]
183
505
 
506
+ begin_artifact_generation
184
507
  report_status("reading #{path}")
185
- ast = Frontend::Parser.new(File.read(path), file: path, mode: @options[:mode]).parse
186
- return emit_ast(ast) if @options[:emit] == "ast"
508
+ resolution = resolve_grammar_path(path)
509
+ @generation_inputs = @last_resolver.source_records
510
+ @generation_sources = @generation_inputs.map(&:path)
511
+ validate_generation_paths!(path, source_paths: resolution.files)
512
+ return emit_ast(resolution.root) if @options[:emit] == "ast"
187
513
 
188
- grammar = Normalizer.new(ast, mode: @options[:mode]).normalize
514
+ grammar = Normalizer.new(resolution, mode: @options[:mode]).normalize
189
515
  dispatch_grammar(grammar, path)
190
516
  end
191
517
 
518
+ # @rbs (String path) -> Frontend::Resolution
519
+ def resolve_grammar_path(path)
520
+ loader = Frontend::SourceLoader.new(record_reads: true)
521
+ @last_resolver = Frontend::Resolver.new(path, mode: @options[:mode], loader: loader)
522
+ @last_resolver.resolve
523
+ end
524
+
525
+ # @rbs (String path) -> IR::Grammar
526
+ def normalize_grammar_path(path)
527
+ Normalizer.new(resolve_grammar_path(path), mode: @options[:mode]).normalize
528
+ end
529
+
192
530
  # @rbs (String path) -> Integer
193
531
  def process_ir(path)
532
+ require_relative "ir/validator"
533
+
534
+ begin_artifact_generation
194
535
  report_status("reading #{path}")
195
- value = IR::Serialize.load(File.read(path))
536
+ source = File.binread(path)
537
+ input = record_generation_input(path, source)
538
+ validate_generation_paths!(path, source_paths: [input.path])
539
+ value = IR::Validator.validate(source)
196
540
  expected = @options[:from] == "grammar-ir" ? IR::Grammar : IR::Automaton
197
541
  raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input" unless value.is_a?(expected)
198
542
 
199
543
  return dispatch_grammar(value, path) if value.is_a?(IR::Grammar)
544
+ return dispatch_automaton(value, path) if value.is_a?(IR::Automaton)
200
545
 
201
- dispatch_automaton(value, path)
546
+ raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input"
202
547
  end
203
548
 
204
549
  # @rbs (IR::Grammar grammar, String path) -> Integer
205
550
  def dispatch_grammar(grammar, path)
206
551
  handle_grammar_warnings(grammar, path)
207
- return 0 if @options[:check_only]
552
+ if @options[:check_only]
553
+ build_automaton(grammar, path) if @options[:warnings]&.include?(:error)
554
+ return 0
555
+ end
556
+
557
+ write_railroad(grammar) unless @options[:verify_output]
558
+ return emit_sets(grammar) if @options[:emit] == "sets"
559
+ return emit_lexer(grammar) if @options[:emit] == "lexer-ir"
208
560
  return emit_grammar(grammar) if @options[:emit] == "grammar-ir"
209
561
  return emit_automaton(grammar, path) if @options[:emit] == "automaton-ir"
210
562
  return emit_ruby(grammar, path) if @options[:emit] == "ruby"
@@ -216,6 +568,10 @@ module Ibex
216
568
  def dispatch_automaton(automaton, path)
217
569
  handle_grammar_warnings(automaton.grammar, path)
218
570
  return 0 if @options[:check_only]
571
+
572
+ write_railroad(automaton.grammar) unless @options[:verify_output]
573
+ return emit_sets(automaton.grammar) if @options[:emit] == "sets"
574
+ return emit_lexer(automaton.grammar) if @options[:emit] == "lexer-ir"
219
575
  return emit_grammar(automaton.grammar) if @options[:emit] == "grammar-ir"
220
576
  return emit_loaded_automaton(automaton, path) if @options[:emit] == "automaton-ir"
221
577
 
@@ -253,13 +609,40 @@ module Ibex
253
609
 
254
610
  # @rbs (IR::Grammar grammar) -> Integer
255
611
  def emit_grammar(grammar)
612
+ finish_artifact_generation(@generation_sources)
256
613
  @stdout.write(IR::Serialize.dump(grammar))
257
614
  0
258
615
  end
259
616
 
617
+ # @rbs (IR::Grammar grammar) -> Integer
618
+ def emit_lexer(grammar)
619
+ lexer = grammar.lexer
620
+ raise Ibex::Error, "(cli):1:1: grammar does not declare a lexer" unless lexer
621
+
622
+ finish_artifact_generation(@generation_sources)
623
+ @stdout.write(IR::Serialize.dump(lexer))
624
+ 0
625
+ end
626
+
627
+ # @rbs (IR::Grammar grammar) -> Integer
628
+ def emit_sets(grammar)
629
+ sets = Analysis::Sets.new(grammar)
630
+ nonterminals = grammar.nonterminals.sort_by(&:name)
631
+ output = {
632
+ nullable: nonterminals.filter_map { |symbol| symbol.name if sets.nullable?(symbol) },
633
+ first: nonterminals.to_h { |symbol| [symbol.name, sets.first(symbol).sort] },
634
+ follow: nonterminals.to_h { |symbol| [symbol.name, sets.follow(symbol).sort] }
635
+ }
636
+ finish_artifact_generation(@generation_sources)
637
+ @stdout.puts(JSON.pretty_generate(output))
638
+ 0
639
+ end
640
+
260
641
  # @rbs (IR::Grammar grammar, String input_path) -> Integer
261
642
  def emit_automaton(grammar, input_path)
262
- @stdout.write(IR::Serialize.dump(build_automaton(grammar, input_path)))
643
+ automaton = build_automaton(grammar, input_path)
644
+ finish_artifact_generation(@generation_sources)
645
+ @stdout.write(IR::Serialize.dump(automaton))
263
646
  0
264
647
  end
265
648
 
@@ -276,57 +659,143 @@ module Ibex
276
659
  line_convert: @options.fetch(:line_convert), debug: @options.fetch(:debug, false),
277
660
  line_convert_all: @options.fetch(:line_convert_all, false),
278
661
  omit_action_call: @options[:omit_actions], superclass: @options[:superclass],
279
- executable: @options[:executable]
662
+ executable: @options[:executable], cst_trivia: @options.fetch(:cst_trivia, :leading),
663
+ error_messages: configured_error_messages(automaton)
280
664
  ).generate
281
665
  output_path = @options[:output] || default_output_path(input_path, ".rb")
282
- File.write(output_path, source)
283
- File.chmod(0o755, output_path) if @options[:executable]
284
- report_status("wrote #{output_path}")
666
+ action_source = action_source_source(automaton) if @options[:action_source]
667
+ write_action_source(output_path, action_source) if action_source
668
+ register_artifact(:parser, output_path, source, mode: (0o755 if @options[:executable]), status: true)
285
669
  write_rbs(automaton, output_path) if @options[:rbs]
670
+ finish_artifact_generation(@generation_sources)
671
+ end
672
+
673
+ # @rbs (IR::Automaton automaton, String output_path, String source, String? action_source) -> Integer
674
+ def verify_generated_outputs(automaton, output_path, source, action_source)
675
+ verify_file(output_path, source, "parser")
676
+ verify_file(rbs_output_path(output_path), rbs_source(automaton), "RBS signature") if @options[:rbs]
677
+ verify_file(action_source_output_path(output_path), action_source, "action source") if action_source
678
+ report_status("verified #{output_path}")
286
679
  0
287
680
  end
288
681
 
682
+ # @rbs (String path, String source, String label) -> void
683
+ def verify_file(path, source, label)
684
+ raise Ibex::Error, "#{path}:1:1: generated #{label} is missing" unless File.exist?(path)
685
+ return if File.binread(path) == source
686
+
687
+ raise Ibex::Error, "#{path}:1:1: generated #{label} is stale; regenerate it with the same options"
688
+ end
689
+
289
690
  # @rbs (IR::Automaton automaton, String output_path) -> void
290
691
  def write_rbs(automaton, output_path)
692
+ path = rbs_output_path(output_path)
693
+ source = rbs_source(automaton)
694
+ register_artifact(:rbs, path, source, status: true)
695
+ end
696
+
697
+ # @rbs (String output_path) -> String
698
+ def rbs_output_path(output_path)
291
699
  configured_path = @options[:rbs]
292
700
  path = configured_path == true ? default_output_path(output_path, ".rbs") : configured_path
293
701
  raise ArgumentError, "RBS output path is required" unless path.is_a?(String)
294
702
 
295
- source = Codegen::RBS.new(automaton, superclass: @options[:superclass]).generate
296
- File.write(path, source)
297
- report_status("wrote #{path}")
703
+ path
704
+ end
705
+
706
+ # @rbs (IR::Automaton automaton) -> String
707
+ def rbs_source(automaton)
708
+ require_relative "codegen/rbs"
709
+ Codegen::RBS.new(
710
+ automaton, superclass: @options[:superclass], omit_action_call: @options[:omit_actions]
711
+ ).generate
712
+ end
713
+
714
+ # @rbs (String output_path, String source) -> void
715
+ def write_action_source(output_path, source)
716
+ path = action_source_output_path(output_path)
717
+ register_artifact(:action_source, path, source, status: true)
718
+ end
719
+
720
+ # @rbs (String output_path) -> String
721
+ def action_source_output_path(output_path)
722
+ configured_path = @options[:action_source]
723
+ path = configured_path == true ? default_output_path(output_path, ".actions.rb") : configured_path
724
+ raise Ibex::Error, "(cli):1:1: action source output path is required" unless path.is_a?(String) && !path.empty?
725
+
726
+ path
727
+ end
728
+
729
+ # @rbs (IR::Automaton automaton) -> String
730
+ def action_source_source(automaton)
731
+ require_relative "codegen/action_source"
732
+ Codegen::ActionSource.new(automaton, omit_action_call: @options[:omit_actions]).generate
298
733
  end
299
734
 
300
735
  # @rbs (IR::Automaton automaton, String input_path) -> Integer
301
736
  def emit_loaded_automaton(automaton, input_path)
302
737
  prepare_loaded_automaton(automaton, input_path)
738
+ finish_artifact_generation(@generation_sources)
303
739
  @stdout.write(IR::Serialize.dump(automaton))
304
740
  0
305
741
  end
306
742
 
307
743
  # @rbs (IR::Grammar grammar, String input_path) -> IR::Automaton
308
744
  def build_automaton(grammar, input_path)
309
- report_status("building LALR automaton")
310
- automaton = LALR::Builder.new(grammar, algorithm: @options[:algorithm] || :lalr).build
745
+ algorithm = @options[:algorithm] || :lalr
746
+ report_status("building #{algorithm} automaton")
747
+ automaton = LALR::Builder.new(
748
+ grammar, algorithm: algorithm, entry_isolation: @options[:entry_isolation] == true
749
+ ).build
311
750
  report_conflicts(automaton, input_path)
312
- write_report(automaton, input_path) if @options[:verbose]
313
- write_visualizations(automaton)
751
+ suggest_ielr(automaton, input_path)
752
+ write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
753
+ write_visualizations(automaton) unless @options[:verify_output]
314
754
  automaton
315
755
  end
316
756
 
317
757
  # @rbs (IR::Automaton automaton, String input_path) -> void
318
758
  def prepare_loaded_automaton(automaton, input_path)
319
759
  report_conflicts(automaton, input_path)
320
- write_report(automaton, input_path) if @options[:verbose]
321
- write_visualizations(automaton)
760
+ suggest_ielr(automaton, input_path)
761
+ write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
762
+ write_visualizations(automaton) unless @options[:verify_output]
322
763
  end
323
764
 
324
765
  # @rbs (IR::Automaton automaton) -> void
325
766
  def write_visualizations(automaton)
326
767
  dot_path = @options[:dot]
768
+ mermaid_path = @options[:mermaid]
327
769
  html_path = @options[:html]
328
- File.write(dot_path, Codegen::Dot.render(automaton)) if dot_path
329
- File.write(html_path, Codegen::HTML.render(automaton)) if html_path
770
+ if dot_path
771
+ require_relative "codegen/dot"
772
+ register_artifact(:dot, dot_path, Codegen::Dot.render(automaton))
773
+ end
774
+ if mermaid_path
775
+ require_relative "codegen/mermaid"
776
+ register_artifact(:mermaid, mermaid_path, Codegen::Mermaid.render(automaton))
777
+ end
778
+ return unless html_path
779
+
780
+ require_relative "codegen/html"
781
+ register_artifact(:html, html_path, Codegen::HTML.render(automaton))
782
+ end
783
+
784
+ # @rbs (IR::Grammar grammar) -> void
785
+ def write_railroad(grammar)
786
+ path = @options[:railroad]
787
+ return unless path
788
+
789
+ require_relative "codegen/railroad"
790
+ register_artifact(:railroad, path, Codegen::Railroad.render(grammar))
791
+ end
792
+
793
+ # @rbs (String output_path) -> String
794
+ def manifest_output_path_for(output_path)
795
+ configured = @options[:manifest]
796
+ return configured if configured.is_a?(String) && !configured.empty?
797
+
798
+ default_output_path(output_path, ".ibex.json")
330
799
  end
331
800
  end
332
801
  # rubocop:enable Metrics/ClassLength