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,75 @@
1
+ class Examples::TinyLanguageParser
2
+ pragma extended
3
+ token IDENTIFIER NUMBER PRINT
4
+ %test accept "value=1+2*3; print value;"
5
+ %test accept "value=(8-2)/3;"
6
+ %test reject "print ;"
7
+ rule
8
+ program : statements { result = val[0] }
9
+ statements : statements statement { result = val[0] + [val[1]] }
10
+ | { result = [] }
11
+ statement : IDENTIFIER '=' expression ';' { result = [:assign, val[0], val[2]] }
12
+ | PRINT expression ';' { result = [:print, val[1]] }
13
+ expression : expression '+' term { result = [:add, val[0], val[2]] }
14
+ | expression '-' term { result = [:subtract, val[0], val[2]] }
15
+ | term { result = val[0] }
16
+ term : term '*' factor { result = [:multiply, val[0], val[2]] }
17
+ | term '/' factor { result = [:divide, val[0], val[2]] }
18
+ | factor { result = val[0] }
19
+ factor : NUMBER { result = [:number, val[0]] }
20
+ | IDENTIFIER { result = [:variable, val[0]] }
21
+ | '(' expression ')' { result = val[1] }
22
+ end
23
+ ---- header
24
+ require "strscan"
25
+ ---- inner
26
+ def parse(source)
27
+ @scanner = StringScanner.new(source)
28
+ do_parse
29
+ end
30
+
31
+ def next_token
32
+ @scanner.skip(/\s+/)
33
+ return false if @scanner.eos?
34
+
35
+ if @scanner.scan(/print\b/)
36
+ [:PRINT, "print"]
37
+ elsif (identifier = @scanner.scan(/[A-Za-z_][A-Za-z0-9_]*/))
38
+ [:IDENTIFIER, identifier]
39
+ elsif (number = @scanner.scan(/\d+/))
40
+ [:NUMBER, Integer(number, 10)]
41
+ elsif (punctuation = @scanner.scan(/[=;()+*\/-]/))
42
+ [punctuation, nil]
43
+ else
44
+ raise ArgumentError, "unexpected language input at offset #{@scanner.pos}"
45
+ end
46
+ end
47
+
48
+ def execute(statements, output: $stdout)
49
+ environment = {}
50
+ statements.each do |statement|
51
+ case statement[0]
52
+ when :assign
53
+ environment[statement[1]] = evaluate_expression(statement[2], environment)
54
+ when :print
55
+ output.puts(evaluate_expression(statement[1], environment))
56
+ end
57
+ end
58
+ environment
59
+ end
60
+
61
+ def evaluate_expression(expression, environment)
62
+ case expression[0]
63
+ when :number then expression[1]
64
+ when :variable then environment.fetch(expression[1])
65
+ when :add then evaluate_expression(expression[1], environment) + evaluate_expression(expression[2], environment)
66
+ when :subtract then evaluate_expression(expression[1], environment) - evaluate_expression(expression[2], environment)
67
+ when :multiply then evaluate_expression(expression[1], environment) * evaluate_expression(expression[2], environment)
68
+ when :divide then evaluate_expression(expression[1], environment) / evaluate_expression(expression[2], environment)
69
+ end
70
+ end
71
+ ---- footer
72
+ if $PROGRAM_NAME == __FILE__
73
+ parser = Examples::TinyLanguageParser.new
74
+ parser.execute(parser.parse(ARGF.read))
75
+ end
@@ -104,11 +104,12 @@ module Ibex
104
104
 
105
105
  # @rbs () -> void
106
106
  def compute_follow
107
- start_definition = @grammar.symbol(@grammar.start)
108
- raise Ibex::Error, "(analysis):1:1: unknown start symbol #{@grammar.start}" unless start_definition
107
+ @grammar.starts.each do |name|
108
+ start_definition = @grammar.symbol(name)
109
+ raise Ibex::Error, "(analysis):1:1: unknown start symbol #{name}" unless start_definition
109
110
 
110
- start_id = start_definition.id
111
- @follow_bits[start_id] |= bit(0)
111
+ @follow_bits[start_definition.id] |= bit(0)
112
+ end
112
113
  dependencies = Array.new(@grammar.symbols.length) { [] }
113
114
  @grammar.productions.each { |production| initialize_follow(production, dependencies) }
114
115
  seeds = @grammar.nonterminals.filter_map { |symbol| symbol.id unless @follow_bits[symbol.id].zero? }
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # One fully rendered file in a parser generation.
6
+ class Artifact
7
+ attr_reader :kind #: Symbol
8
+ attr_reader :path #: String
9
+ attr_reader :content #: String
10
+ attr_reader :mode #: Integer?
11
+
12
+ # @rbs (kind: Symbol, path: String, content: String, ?mode: Integer?) -> void
13
+ def initialize(kind:, path:, content:, mode: nil)
14
+ raise ArgumentError, "artifact path must not be empty" if path.empty?
15
+
16
+ @kind = kind
17
+ @path = path.dup.freeze
18
+ @content = content.dup.freeze
19
+ @mode = mode
20
+ freeze
21
+ end
22
+ end
23
+
24
+ # A generation's rendered outputs before any target is changed.
25
+ class ArtifactSet
26
+ # @rbs! include Enumerable[Artifact]
27
+ # @rbs skip
28
+ include Enumerable
29
+
30
+ # @rbs () -> void
31
+ def initialize
32
+ @artifacts = [] #: Array[Artifact]
33
+ end
34
+
35
+ # @rbs (kind: Symbol, path: String, content: String, ?mode: Integer?) -> Artifact
36
+ def add(kind:, path:, content:, mode: nil)
37
+ artifact = Artifact.new(kind: kind, path: path, content: content, mode: mode)
38
+ @artifacts << artifact
39
+ artifact
40
+ end
41
+
42
+ # @rbs!
43
+ # def each: () -> Enumerator[Artifact, ArtifactSet]
44
+ # | () { (Artifact) -> void } -> ArtifactSet
45
+ # @rbs skip
46
+ def each(&block)
47
+ return enum_for(:each) unless block
48
+
49
+ @artifacts.each(&block)
50
+ self
51
+ end
52
+
53
+ # @rbs () -> Array[Artifact]
54
+ def to_a
55
+ @artifacts.dup
56
+ end
57
+
58
+ # @rbs () -> bool
59
+ def empty?
60
+ @artifacts.empty?
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "optparse"
5
+ require_relative "../codegen/ambiguity"
6
+
7
+ module Ibex
8
+ # Bounded grammar ambiguity checking for CI and human inspection.
9
+ module CLIAmbiguity
10
+ # @rbs!
11
+ # private def print_help: (OptionParser) -> Integer
12
+ # private def input_path: (Array[String]) -> String
13
+ # private def positive_counterexample_limit: (Integer, String) -> Integer
14
+ # private def normalize_grammar_path: (String) -> IR::Grammar
15
+
16
+ private
17
+
18
+ # @rbs (Array[String] arguments) -> Integer
19
+ def run_check_command(arguments)
20
+ parser = ambiguity_option_parser
21
+ remaining = parser.parse(arguments)
22
+ return print_help(parser) if @options[:help]
23
+ raise Ibex::Error, "(cli):1:1: check command requires --ambiguity" unless @options[:check_ambiguity]
24
+
25
+ grammar = normalize_grammar_path(input_path(remaining))
26
+ automaton = LALR::Builder.new(
27
+ grammar, algorithm: @options[:algorithm] || :lalr, entry_isolation: @options[:entry_isolation] == true
28
+ ).build
29
+ check = Codegen::Ambiguity.new(
30
+ automaton,
31
+ max_tokens: @options.fetch(:counterexample_max_tokens),
32
+ max_configurations: @options.fetch(:counterexample_max_configurations)
33
+ )
34
+ output = @options[:check_format] == "json" ? JSON.pretty_generate(check.to_h) : check.render_text
35
+ @stdout.puts(output)
36
+ check.exit_status
37
+ end
38
+
39
+ # @rbs () -> OptionParser
40
+ def ambiguity_option_parser
41
+ OptionParser.new do |options|
42
+ options.banner = "Usage: ibex check --ambiguity [options] grammarfile"
43
+ options.on("--ambiguity", "search parser conflicts for ambiguous sentences") do
44
+ @options[:check_ambiguity] = true
45
+ end
46
+ options.on("--max-tokens=N", Integer, "maximum sentence length (default: 32)") do |value|
47
+ @options[:counterexample_max_tokens] = positive_counterexample_limit(value, "--max-tokens")
48
+ end
49
+ options.on("--max-configurations=N", Integer, "maximum configurations per conflict (default: 50000)") do |value|
50
+ @options[:counterexample_max_configurations] = positive_counterexample_limit(value, "--max-configurations")
51
+ end
52
+ options.on("--format=FORMAT", %w[text json], "text or json (default: text)") do |value|
53
+ @options[:check_format] = value
54
+ end
55
+ options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
56
+ @options[:algorithm] = value.to_sym
57
+ end
58
+ options.on("--entry-isolation", "build independent state sets for each start symbol") do
59
+ @options[:entry_isolation] = true
60
+ end
61
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
62
+ options.on("--help", "show help") { @options[:help] = true }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../lalr/conflict_search_limits"
4
+
3
5
  module Ibex
4
6
  # Counterexample report limits and their command-line validation.
5
7
  module CLICounterexampleOptions
8
+ # @rbs @options: cli_options
9
+
6
10
  DEFAULTS = {
7
- counterexample_max_tokens: LALR::Counterexample::DEFAULT_MAX_TOKENS,
8
- counterexample_max_configurations: LALR::Counterexample::DEFAULT_MAX_CONFIGURATIONS
11
+ counterexample_max_tokens: LALR::ConflictSearchLimits::DEFAULT_MAX_TOKENS,
12
+ counterexample_max_configurations: LALR::ConflictSearchLimits::DEFAULT_MAX_CONFIGURATIONS
9
13
  }.freeze #: Hash[Symbol, Integer]
10
14
 
11
15
  private
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "tempfile"
5
+ require_relative "../coverage"
6
+
7
+ module Ibex
8
+ # CLI collection, merge, and threshold checks for runtime coverage.
9
+ module CLICoverage
10
+ # @rbs!
11
+ # private def same_file_target?: (String left, String right) -> bool
12
+ #
13
+ # type coverage_output_options = {
14
+ # paths: Array[String],
15
+ # ?output: String
16
+ # }
17
+ # type coverage_check_options = {
18
+ # paths: Array[String],
19
+ # min_states: Float,
20
+ # min_productions: Float
21
+ # }
22
+
23
+ private
24
+
25
+ # @rbs (Array[String] arguments) -> Integer
26
+ def run_coverage_command(arguments)
27
+ operation = arguments.shift
28
+ case operation
29
+ when "collect" then run_coverage_collect(arguments)
30
+ when "merge" then run_coverage_merge(arguments)
31
+ when "check" then run_coverage_check(arguments)
32
+ else
33
+ raise Ibex::Error, "(cli):1:1: coverage requires collect, merge, or check"
34
+ end
35
+ end
36
+
37
+ # @rbs (Array[String] arguments) -> Integer
38
+ def run_coverage_collect(arguments)
39
+ settings = coverage_output_options(arguments, "collect", "INPUT")
40
+ input = single_coverage_path(settings.fetch(:paths), "coverage collect")
41
+ reject_coverage_output_collision([input], settings[:output])
42
+ report = Coverage::Collector.collect_file(input)
43
+ write_coverage_report(report, settings[:output])
44
+ 0
45
+ end
46
+
47
+ # @rbs (Array[String] arguments) -> Integer
48
+ def run_coverage_merge(arguments)
49
+ settings = coverage_output_options(arguments, "merge", "INPUT...")
50
+ inputs = settings.fetch(:paths)
51
+ raise Ibex::Error, "(cli):1:1: coverage merge requires at least one input" if inputs.empty?
52
+
53
+ reject_coverage_output_collision(inputs, settings[:output])
54
+ reports = inputs.map { |path| Coverage::Report.load_file(path) }
55
+ report = Coverage::Report.merge(reports)
56
+ write_coverage_report(report, settings[:output])
57
+ 0
58
+ rescue ArgumentError => e
59
+ raise Ibex::Error, "(coverage):1:1: #{e.message}"
60
+ end
61
+
62
+ # @rbs (Array[String] arguments) -> Integer
63
+ def run_coverage_check(arguments)
64
+ settings = coverage_check_options(arguments)
65
+ path = single_coverage_path(settings.fetch(:paths), "coverage check")
66
+ report = Coverage::Report.load_file(path)
67
+ state_percentage = coverage_percentage(report.state_hits.length, report.state_count)
68
+ production_percentage = coverage_percentage(report.production_hits.length, report.production_count)
69
+ passed = state_percentage >= settings.fetch(:min_states) &&
70
+ production_percentage >= settings.fetch(:min_productions)
71
+ summary = format(
72
+ "coverage %<status>s: states %<states>.2f%% (%<state_hits>d/%<state_total>d), " \
73
+ "productions %<productions>.2f%% (%<production_hits>d/%<production_total>d)",
74
+ status: passed ? "ok" : "failed",
75
+ states: state_percentage,
76
+ state_hits: report.state_hits.length,
77
+ state_total: report.state_count,
78
+ productions: production_percentage,
79
+ production_hits: report.production_hits.length,
80
+ production_total: report.production_count
81
+ )
82
+ (passed ? @stdout : @stderr).puts(summary)
83
+ passed ? 0 : 1
84
+ end
85
+
86
+ # @rbs (Array[String] arguments, String command, String operands) -> coverage_output_options
87
+ def coverage_output_options(arguments, command, operands)
88
+ settings = { paths: [] } #: coverage_output_options
89
+ parser = OptionParser.new do |options|
90
+ options.banner = "Usage: ibex coverage #{command} #{operands} [-o FILE]"
91
+ options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
92
+ end
93
+ settings[:paths] = parser.parse(arguments)
94
+ settings
95
+ end
96
+
97
+ # @rbs (Array[String] arguments) -> coverage_check_options
98
+ def coverage_check_options(arguments)
99
+ settings = { paths: [], min_states: 0.0, min_productions: 0.0 } #: coverage_check_options
100
+ parser = OptionParser.new do |options|
101
+ options.banner = "Usage: ibex coverage check REPORT [thresholds]"
102
+ options.on("--min-states=PERCENT", Float, "minimum visited states") do |value|
103
+ settings[:min_states] = coverage_threshold(value, "min-states")
104
+ end
105
+ options.on("--min-productions=PERCENT", Float, "minimum reduced productions") do |value|
106
+ settings[:min_productions] = coverage_threshold(value, "min-productions")
107
+ end
108
+ end
109
+ settings[:paths] = parser.parse(arguments)
110
+ settings
111
+ end
112
+
113
+ # @rbs (Float value, String name) -> Float
114
+ def coverage_threshold(value, name)
115
+ return value if value.finite? && value >= 0.0 && value <= 100.0
116
+
117
+ raise OptionParser::InvalidArgument, "--#{name} must be between 0 and 100"
118
+ end
119
+
120
+ # @rbs (Integer covered, Integer total) -> Float
121
+ def coverage_percentage(covered, total)
122
+ return 100.0 if total.zero?
123
+
124
+ covered.fdiv(total) * 100.0
125
+ end
126
+
127
+ # @rbs (Array[String] arguments, String command) -> String
128
+ def single_coverage_path(arguments, command)
129
+ return arguments.first if arguments.length == 1
130
+
131
+ raise Ibex::Error, "(cli):1:1: #{command} requires exactly one input"
132
+ end
133
+
134
+ # @rbs (Array[String] inputs, String? output) -> void
135
+ def reject_coverage_output_collision(inputs, output)
136
+ return unless output
137
+ return unless inputs.any? { |input| same_file_target?(input, output) }
138
+
139
+ raise Ibex::Error, "(cli):1:1: coverage input and output paths must be distinct"
140
+ end
141
+
142
+ # @rbs (Coverage::Report report, String? output) -> void
143
+ def write_coverage_report(report, output)
144
+ source = report.to_json
145
+ return @stdout.write(source) unless output
146
+
147
+ atomic_write_coverage(output, source)
148
+ end
149
+
150
+ # @rbs (String path, String source) -> void
151
+ def atomic_write_coverage(path, source)
152
+ target_path = File.symlink?(path) ? File.realpath(path) : path
153
+ directory = File.dirname(File.expand_path(target_path))
154
+ basename = File.basename(target_path)
155
+ Tempfile.create([".#{basename}.", ".tmp"], directory) do |temporary|
156
+ temporary.binmode
157
+ temporary.write(source)
158
+ temporary.flush
159
+ temporary.fsync
160
+ temporary.chmod(coverage_file_mode(target_path))
161
+ temporary.close
162
+ File.rename(temporary.path, target_path)
163
+ end
164
+ end
165
+
166
+ # @rbs (String path) -> Integer
167
+ def coverage_file_mode(path)
168
+ return File.stat(path).mode & 0o777 if File.exist?(path)
169
+
170
+ 0o666 & ~File.umask
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "optparse"
5
+ require_relative "../table_simulation"
6
+
7
+ module Ibex
8
+ # Safe parser-table simulation subcommand.
9
+ module CLIDebug
10
+ # @rbs!
11
+ # type debug_options = {
12
+ # paths: Array[String],
13
+ # format: String,
14
+ # max_steps: Integer,
15
+ # max_stack: Integer
16
+ # }
17
+
18
+ private
19
+
20
+ # @rbs (Array[String] arguments) -> Integer
21
+ def run_debug_command(arguments)
22
+ settings = debug_options(arguments)
23
+ operands = settings.fetch(:paths)
24
+ raise Ibex::Error, "(cli):1:1: debug requires an Automaton IR file" if operands.empty?
25
+
26
+ path = operands.first
27
+ value = IR::Validator.validate(File.read(path))
28
+ raise Ibex::Error, "#{path}:1:1: debug requires Automaton IR" unless value.is_a?(IR::Automaton)
29
+
30
+ simulator = TableSimulation::Simulator.new(
31
+ value,
32
+ max_steps: settings.fetch(:max_steps),
33
+ max_stack: settings.fetch(:max_stack)
34
+ )
35
+ tokens = operands.drop(1)
36
+ interactive = tokens.empty?
37
+ format = settings.fetch(:format)
38
+ result = interactive ? simulate_debug_input(simulator, format) : simulator.simulate(tokens)
39
+ write_debug_result(result, format, steps_written: interactive && format == "text")
40
+ result.status == :accepted ? 0 : 1
41
+ rescue ArgumentError => e
42
+ raise Ibex::Error, "(debug):1:1: #{e.message}"
43
+ end
44
+
45
+ # @rbs (Array[String] arguments) -> debug_options
46
+ def debug_options(arguments)
47
+ settings = {
48
+ paths: [],
49
+ format: "text",
50
+ max_steps: TableSimulation::Simulator::DEFAULT_MAX_STEPS,
51
+ max_stack: TableSimulation::Simulator::DEFAULT_MAX_STACK
52
+ } #: debug_options
53
+ parser = OptionParser.new do |options|
54
+ options.banner = "Usage: ibex debug AUTOMATON.json [TOKEN...]"
55
+ options.on("--format=FORMAT", %w[text json], "text or json") { |value| settings[:format] = value }
56
+ options.on("--max-steps=N", Integer, "maximum simulated actions") do |value|
57
+ settings[:max_steps] = positive_debug_budget(value, "max-steps")
58
+ end
59
+ options.on("--max-stack=N", Integer, "maximum simulated state stack depth") do |value|
60
+ settings[:max_stack] = positive_debug_budget(value, "max-stack")
61
+ end
62
+ end
63
+ settings[:paths] = parser.parse(arguments)
64
+ settings
65
+ end
66
+
67
+ # @rbs (Integer value, String name) -> Integer
68
+ def positive_debug_budget(value, name)
69
+ return value if value.positive?
70
+
71
+ raise OptionParser::InvalidArgument, "--#{name} must be positive"
72
+ end
73
+
74
+ # @rbs (TableSimulation::Simulator simulator, String format) -> TableSimulation::Result
75
+ def simulate_debug_input(simulator, format)
76
+ session = simulator.start
77
+ while (line = @stdin.gets)
78
+ spelling = line.strip
79
+ break if spelling.empty?
80
+
81
+ steps = session.push(spelling)
82
+ write_debug_steps(steps) if format == "text"
83
+ break if session.status
84
+ end
85
+ written = session.steps.length
86
+ result = session.finish
87
+ write_debug_steps(result.steps.drop(written)) if format == "text"
88
+ result
89
+ end
90
+
91
+ # @rbs (TableSimulation::Result result, String format, steps_written: bool) -> void
92
+ def write_debug_result(result, format, steps_written:)
93
+ if format == "json"
94
+ @stdout.write(result.to_json)
95
+ else
96
+ write_debug_steps(result.steps) unless steps_written
97
+ @stdout.puts("status=#{result.status}")
98
+ end
99
+ end
100
+
101
+ # @rbs (Array[TableSimulation::Step] steps) -> void
102
+ def write_debug_steps(steps)
103
+ steps.each { |step| @stdout.puts(TableSimulation::Text.render_step(step)) }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "optparse"
5
+ require_relative "../frontend"
6
+
7
+ module Ibex
8
+ # Batch frontend diagnostics subcommand.
9
+ module CLIDiagnostics
10
+ DIAGNOSTICS_SCHEMA_VERSION = 1 #: Integer
11
+ DEFAULT_MAX_DIAGNOSTICS = 20 #: Integer
12
+
13
+ # @rbs!
14
+ # type diagnostic_settings = {
15
+ # format: String,
16
+ # max_diagnostics: Integer,
17
+ # mode: Symbol,
18
+ # ?help: bool
19
+ # }
20
+ #
21
+ # private def input_path: (Array[String]) -> String
22
+
23
+ private
24
+
25
+ # @rbs (Array[String] arguments) -> Integer
26
+ def run_diagnose_command(arguments)
27
+ settings = {
28
+ format: "text", max_diagnostics: DEFAULT_MAX_DIAGNOSTICS, mode: :default
29
+ } #: diagnostic_settings
30
+ parser = diagnostics_option_parser(settings)
31
+ remaining = parser.parse(arguments)
32
+ if settings[:help]
33
+ @stdout.puts(parser)
34
+ return 0
35
+ end
36
+
37
+ path = input_path(remaining)
38
+ result = Frontend::Parser.new(File.binread(path), file: path, mode: settings.fetch(:mode))
39
+ .parse_with_diagnostics(max_diagnostics: settings.fetch(:max_diagnostics))
40
+ result = resolve_diagnostic_result(result, path, settings.fetch(:mode)) if result.success?
41
+ write_diagnostics(result, format: settings.fetch(:format))
42
+ result.success? ? 0 : 1
43
+ end
44
+
45
+ # Cross-file recovery is deliberately bounded to the first resolver error.
46
+ # @rbs (Frontend::ParseResult result, String path, Symbol mode) -> Frontend::ParseResult
47
+ def resolve_diagnostic_result(result, path, mode)
48
+ Frontend::Resolver.new(path, mode: mode).resolve
49
+ result
50
+ rescue Frontend::ResolutionIOError
51
+ raise
52
+ rescue Ibex::Error => e
53
+ Frontend::ParseResult.new(diagnostics: [resolution_diagnostic(e, path)], ast: nil, document: nil)
54
+ end
55
+
56
+ # @rbs (Ibex::Error error, String fallback_file) -> Frontend::Diagnostic
57
+ def resolution_diagnostic(error, fallback_file)
58
+ match = error.message.match(/\A(.+):(\d+):(\d+): (.*)\z/m)
59
+ location = if match
60
+ Frontend::Location.new(
61
+ file: match[1].to_s, line: Integer(match[2].to_s), column: Integer(match[3].to_s)
62
+ )
63
+ else
64
+ Frontend::Location.new(file: fallback_file, line: 1, column: 1)
65
+ end
66
+ message = match ? match[4].to_s : error.message
67
+ Frontend::Diagnostic.new(
68
+ code: "frontend.resolution_error", phase: :syntax, message: message,
69
+ location: location, rendered: error.message
70
+ )
71
+ end
72
+
73
+ # @rbs (diagnostic_settings settings) -> OptionParser
74
+ def diagnostics_option_parser(settings)
75
+ OptionParser.new do |options|
76
+ options.banner = "Usage: ibex diagnose [options] grammarfile"
77
+ options.on("--format=FORMAT", %w[text json], "text or json") { |value| settings[:format] = value }
78
+ options.on("--max-diagnostics=N", "positive diagnostic limit") do |value|
79
+ settings[:max_diagnostics] = positive_diagnostic_limit(value)
80
+ end
81
+ options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
82
+ options.on("--help", "show help") { settings[:help] = true }
83
+ end
84
+ end
85
+
86
+ # @rbs (String value) -> Integer
87
+ def positive_diagnostic_limit(value)
88
+ limit = Integer(value, 10)
89
+ return limit if limit.positive?
90
+
91
+ raise OptionParser::InvalidArgument, "max diagnostics must be positive"
92
+ rescue ArgumentError
93
+ raise OptionParser::InvalidArgument, "max diagnostics must be a positive integer"
94
+ end
95
+
96
+ # @rbs (Frontend::ParseResult result, format: String) -> void
97
+ def write_diagnostics(result, format:)
98
+ if format == "json"
99
+ @stdout.puts(JSON.pretty_generate(diagnostics_document(result)))
100
+ else
101
+ result.diagnostics.each { |diagnostic| @stdout.puts(diagnostic.to_s) }
102
+ end
103
+ end
104
+
105
+ # @rbs (Frontend::ParseResult result) -> Hash[String, untyped]
106
+ def diagnostics_document(result)
107
+ {
108
+ "ibex_diagnostics" => "frontend",
109
+ "schema_version" => DIAGNOSTICS_SCHEMA_VERSION,
110
+ "success" => result.success?,
111
+ "ast_available" => !result.ast.nil?,
112
+ "diagnostics" => result.diagnostics.map(&:to_h)
113
+ }
114
+ end
115
+ end
116
+ end