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,196 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ # Canonical target, source, symlink, and lock identity checks for generation.
6
+ module GenerationTransactionValidation
7
+ # @rbs!
8
+ # private def lock_path: (String) -> String
9
+
10
+ private
11
+
12
+ # Follow a final symlink while preserving the symlink path itself during publication.
13
+ # @rbs (String path, ?Hash[String, bool] seen, ?validate_links: bool) -> String
14
+ def resolved_target(path, seen = {}, validate_links: true)
15
+ expanded = File.expand_path(path)
16
+ if File.symlink?(expanded)
17
+ raise Errno::ELOOP, expanded if seen[expanded]
18
+
19
+ seen[expanded] = true
20
+ linked = File.expand_path(File.readlink(expanded), File.dirname(expanded))
21
+ return resolved_target(linked, seen, validate_links: validate_links)
22
+ end
23
+ return existing_target(expanded, validate_links) if File.exist?(expanded)
24
+
25
+ parent = File.realpath(File.dirname(expanded))
26
+ raise Errno::ENOTDIR, parent unless File.directory?(parent)
27
+
28
+ File.join(parent, File.basename(expanded))
29
+ end
30
+
31
+ # @rbs (String expanded, bool validate_links) -> String
32
+ def existing_target(expanded, validate_links)
33
+ target = File.realpath(expanded)
34
+ validate_existing_target!(target) if validate_links
35
+ target
36
+ end
37
+
38
+ # @rbs (String target) -> void
39
+ def validate_existing_target!(target)
40
+ stat = File.stat(target)
41
+ unless stat.file?
42
+ raise GenerationTransaction::Error,
43
+ "(generation):1:1: output target is not a regular file: #{target}"
44
+ end
45
+ return unless stat.nlink > 1
46
+
47
+ raise GenerationTransaction::Error,
48
+ "(generation):1:1: output target has multiple hard links: #{target}"
49
+ end
50
+
51
+ # @rbs () -> void
52
+ def validate_source_boundaries!
53
+ source_paths = @source_records.map(&:path)
54
+ @records.each do |record|
55
+ target = record.fetch(:target)
56
+ collision = source_paths.find { |source| paths_alias?(target, source) }
57
+ next unless collision
58
+
59
+ raise GenerationTransaction::Error,
60
+ "(generation):1:1: artifact target aliases generation input: #{target} (#{collision})"
61
+ end
62
+ end
63
+
64
+ # @rbs (Array[String] lock_paths) -> void
65
+ def validate_lock_boundaries!(lock_paths)
66
+ protected_paths = protected_generation_paths
67
+ lock_paths.each do |path|
68
+ if File.symlink?(path)
69
+ raise GenerationTransaction::Error,
70
+ "(generation):1:1: generation lock must not be a symlink: #{path}"
71
+ end
72
+
73
+ collision = protected_paths.find { |protected| paths_alias?(path, protected) }
74
+ next unless collision
75
+
76
+ raise GenerationTransaction::Error,
77
+ "(generation):1:1: generation lock aliases a protected path: #{path} (#{collision})"
78
+ end
79
+ end
80
+
81
+ # @rbs (String path, File lock) -> void
82
+ def validate_lock_identity!(path, lock)
83
+ path_stat = File.lstat(path)
84
+ lock_stat = lock.stat
85
+ unless stable_lock_identity?(path_stat, lock_stat)
86
+ raise GenerationTransaction::Error,
87
+ "(generation):1:1: generation lock identity changed while opening: #{path}"
88
+ end
89
+ return unless protected_generation_stats.any? { |stat| same_inode?(stat, lock_stat) }
90
+
91
+ raise GenerationTransaction::Error,
92
+ "(generation):1:1: generation lock aliases a protected inode: #{path}"
93
+ end
94
+
95
+ # @rbs () -> Array[String]
96
+ def protected_generation_paths
97
+ @source_records.map(&:path) + @records.map { |record| record.fetch(:target) }
98
+ end
99
+
100
+ # @rbs () -> Array[File::Stat]
101
+ def protected_generation_stats
102
+ protected_generation_paths.filter_map { |path| File.stat(path) if File.exist?(path) }
103
+ end
104
+
105
+ # @rbs (File::Stat path_stat, File::Stat lock_stat) -> bool
106
+ def stable_lock_identity?(path_stat, lock_stat)
107
+ path_stat.file? && path_stat.nlink == 1 && same_inode?(path_stat, lock_stat)
108
+ end
109
+
110
+ # @rbs (File::Stat left, File::Stat right) -> bool
111
+ def same_inode?(left, right)
112
+ left.dev == right.dev && left.ino == right.ino
113
+ end
114
+
115
+ # @rbs (String left, String right) -> bool
116
+ def paths_alias?(left, right)
117
+ return true if File.expand_path(left) == File.expand_path(right)
118
+ return true if portable_target_key(left) == portable_target_key(right)
119
+ return false unless File.exist?(left) && File.exist?(right)
120
+
121
+ File.identical?(left, right)
122
+ rescue SystemCallError
123
+ true
124
+ end
125
+
126
+ # Reject target names that collide on common case-insensitive or
127
+ # normalization-insensitive filesystems, even when the current filesystem
128
+ # would permit both names.
129
+ # @rbs (String path) -> [String, String]
130
+ def portable_target_key(path)
131
+ basename = File.basename(path)
132
+ folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
133
+ basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
134
+ else
135
+ basename.downcase
136
+ end
137
+ [File.dirname(path), folded]
138
+ end
139
+
140
+ # @rbs (String phase) -> void
141
+ def ensure_sources_stable!(phase)
142
+ validate_active_locks!(phase)
143
+ validate_current_targets!(phase)
144
+ return if @stability_check.call
145
+
146
+ raise GenerationTransaction::SourceChanged, "(generation):1:1: source changed #{phase}"
147
+ end
148
+
149
+ # @rbs (String phase) -> void
150
+ def validate_active_locks!(phase)
151
+ @locked_paths.zip(@locks).each do |path, lock|
152
+ next if stable_lock_identity?(File.lstat(path), lock.stat)
153
+
154
+ raise GenerationTransaction::SourceChanged,
155
+ "(generation):1:1: generation lock changed #{phase}: #{path}"
156
+ end
157
+ rescue SystemCallError => e
158
+ raise GenerationTransaction::SourceChanged,
159
+ "(generation):1:1: cannot revalidate generation lock #{phase}: #{e.message}"
160
+ end
161
+
162
+ # @rbs (String phase) -> void
163
+ def validate_current_targets!(phase)
164
+ sources = @source_records.map(&:path)
165
+ @records.each do |record|
166
+ validate_current_target!(record, sources, phase)
167
+ end
168
+ rescue SystemCallError => e
169
+ raise GenerationTransaction::SourceChanged,
170
+ "(generation):1:1: cannot revalidate artifact target #{phase}: #{e.message}"
171
+ end
172
+
173
+ # @rbs (Hash[Symbol, untyped] record, Array[String] sources, String phase) -> void
174
+ def validate_current_target!(record, sources, phase)
175
+ artifact = record.fetch(:artifact)
176
+ current = resolved_target(artifact.path, validate_links: false)
177
+ unless current == record.fetch(:target)
178
+ raise GenerationTransaction::SourceChanged,
179
+ "(generation):1:1: artifact target changed #{phase}: #{artifact.path}"
180
+ end
181
+ return unless sources.any? { |source| paths_alias?(current, source) }
182
+
183
+ raise GenerationTransaction::SourceChanged,
184
+ "(generation):1:1: artifact target aliases an input #{phase}: #{artifact.path}"
185
+ end
186
+
187
+ # @rbs () -> void
188
+ def validate_locked_targets!
189
+ current = @records.map { |record| lock_path(record.fetch(:target)) }.uniq.sort
190
+ return if current == @locked_paths
191
+
192
+ raise GenerationTransaction::Error,
193
+ "(generation):1:1: output target changed while acquiring generation locks"
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rbconfig"
5
+ require "tempfile"
6
+ require "timeout"
7
+ require "tmpdir"
8
+
9
+ module Ibex
10
+ # Executes grammar-declared source examples against one generated parser.
11
+ module GrammarTests
12
+ DEFAULT_TIMEOUT = 10
13
+ RESULT_MARKER = "IBEX_GRAMMAR_TEST_RESULT="
14
+
15
+ Result = Struct.new(
16
+ :expectation, #: Symbol
17
+ :actual, #: Symbol
18
+ :error_class, #: String?
19
+ :error_message, #: String?
20
+ :location, #: IR::location
21
+ :production_ids, #: Array[Integer]
22
+ keyword_init: true
23
+ )
24
+
25
+ class Result
26
+ # @rbs () -> bool
27
+ def passed? = expectation == actual
28
+ end
29
+
30
+ ProductionCoverage = Struct.new(
31
+ :covered_ids, #: Array[Integer]
32
+ :missing_ids, #: Array[Integer]
33
+ :production_count, #: Integer
34
+ keyword_init: true
35
+ )
36
+
37
+ class ProductionCoverage
38
+ # @rbs () -> Float
39
+ def percentage
40
+ return 100.0 if production_count.zero?
41
+
42
+ covered_ids.length.fdiv(production_count) * 100
43
+ end
44
+
45
+ # @rbs (Integer minimum) -> bool
46
+ def meets?(minimum)
47
+ covered_ids.length * 100 >= production_count * minimum
48
+ end
49
+
50
+ # @rbs () -> bool
51
+ def complete? = missing_ids.empty?
52
+ end
53
+
54
+ # Runs all examples in one isolated process while creating a fresh parser
55
+ # instance for every case.
56
+ class Runner
57
+ CHILD_RUNNER = <<~'RUBY'
58
+ require "json"
59
+
60
+ load ARGV.fetch(0)
61
+ parser_class = ARGV.fetch(1).split("::").reject(&:empty?).reduce(Object) do |scope, name|
62
+ scope.const_get(name, false)
63
+ end
64
+ tests = JSON.parse(ARGV.fetch(2))
65
+ results = tests.map do |test|
66
+ production_ids = []
67
+ begin
68
+ parser = parser_class.new
69
+ raise NoMethodError, "#{parser_class} must define parse(source)" unless parser.respond_to?(:parse)
70
+
71
+ parser.observe do |event|
72
+ production_ids << event.data.fetch("production_id") if event.type == :reduce
73
+ end
74
+ parser.parse(test.fetch("source"))
75
+ result = { "actual" => "accept", "error_class" => nil, "error_message" => nil }
76
+ rescue Ibex::Runtime::ParseError => error
77
+ result = { "actual" => "reject", "error_class" => error.class.name, "error_message" => error.message }
78
+ rescue SystemExit, SignalException, StandardError => error
79
+ result = { "actual" => "error", "error_class" => error.class.name, "error_message" => error.message }
80
+ end
81
+ result.merge("production_ids" => production_ids)
82
+ end
83
+ puts "IBEX_GRAMMAR_TEST_RESULT=#{JSON.generate(results)}"
84
+ RUBY
85
+
86
+ # @rbs @automaton: IR::Automaton
87
+ # @rbs @timeout: Integer
88
+
89
+ # @rbs (IR::Automaton automaton, ?timeout: Integer) -> void
90
+ def initialize(automaton, timeout: DEFAULT_TIMEOUT)
91
+ raise ArgumentError, "timeout must be a positive Integer" unless timeout.is_a?(Integer) && timeout.positive?
92
+
93
+ @automaton = automaton
94
+ @timeout = timeout
95
+ end
96
+
97
+ # @rbs () -> Array[Result]
98
+ def run
99
+ grammar = @automaton.grammar
100
+ raise Ibex::Error, "(test):1:1: grammar declares no %test cases" if grammar.grammar_tests.empty?
101
+ unless grammar.parser_parameters.empty?
102
+ raise Ibex::Error, "(test):1:1: %test cannot instantiate a parser with required %param declarations"
103
+ end
104
+
105
+ Dir.mktmpdir("ibex-grammar-tests") do |directory|
106
+ parser_path = File.join(directory, "parser.rb")
107
+ runner_path = File.join(directory, "runner.rb")
108
+ File.binwrite(parser_path, generated_parser)
109
+ File.binwrite(runner_path, CHILD_RUNNER)
110
+ execute_child(parser_path, runner_path)
111
+ end
112
+ end
113
+
114
+ # @rbs (Array[Result] results) -> ProductionCoverage
115
+ def production_coverage(results)
116
+ production_count = @automaton.grammar.productions.length
117
+ covered_ids = results.flat_map(&:production_ids).uniq.sort.freeze
118
+ missing_ids = ((0...production_count).to_a - covered_ids).freeze
119
+ ProductionCoverage.new(
120
+ covered_ids: covered_ids, missing_ids: missing_ids, production_count: production_count
121
+ ).freeze
122
+ end
123
+
124
+ private
125
+
126
+ # @rbs () -> String
127
+ def generated_parser
128
+ Codegen::Ruby.new(@automaton, table: :compact, embedded: true, line_convert: true).generate
129
+ end
130
+
131
+ # @rbs (String parser_path, String runner_path) -> Array[Result]
132
+ def execute_child(parser_path, runner_path)
133
+ payload = JSON.generate(@automaton.grammar.grammar_tests)
134
+ stdout = Tempfile.new("ibex-grammar-test-stdout")
135
+ stderr = Tempfile.new("ibex-grammar-test-stderr")
136
+ stdout_path = stdout.path || raise(Ibex::Error, "(test):1:1: missing temporary stdout path")
137
+ stderr_path = stderr.path || raise(Ibex::Error, "(test):1:1: missing temporary stderr path")
138
+ pid = spawn(
139
+ RbConfig.ruby, runner_path, parser_path, @automaton.grammar.class_name, payload,
140
+ out: stdout_path, err: stderr_path
141
+ )
142
+ status = wait_for_child(pid)
143
+ output = File.binread(stdout_path)
144
+ errors = File.binread(stderr_path)
145
+ raise_child_failure(status, errors) unless status.success?
146
+
147
+ decode_results(output)
148
+ ensure
149
+ stdout&.close!
150
+ stderr&.close!
151
+ end
152
+
153
+ # @rbs (Integer pid) -> Process::Status
154
+ def wait_for_child(pid)
155
+ Timeout.timeout(@timeout) { Process.wait2(pid).fetch(1) }
156
+ rescue Timeout::Error
157
+ Process.kill("KILL", pid)
158
+ Process.wait(pid)
159
+ raise Ibex::Error, "(test):1:1: grammar tests exceeded #{@timeout} seconds"
160
+ end
161
+
162
+ # @rbs (Process::Status status, String errors) -> bot
163
+ def raise_child_failure(status, errors)
164
+ detail = errors.lines.last&.strip
165
+ suffix = detail && !detail.empty? ? ": #{detail}" : ""
166
+ raise Ibex::Error, "(test):1:1: grammar test process exited with status #{status.exitstatus}#{suffix}"
167
+ end
168
+
169
+ # @rbs (String output) -> Array[Result]
170
+ def decode_results(output)
171
+ line = output.lines.reverse.find { |candidate| candidate.start_with?(RESULT_MARKER) }
172
+ raise Ibex::Error, "(test):1:1: grammar test process produced no result" unless line
173
+
174
+ documents = JSON.parse(line.delete_prefix(RESULT_MARKER).strip)
175
+ tests = @automaton.grammar.grammar_tests
176
+ unless documents.is_a?(Array) && documents.length == tests.length
177
+ raise Ibex::Error, "(test):1:1: grammar test process returned an invalid result count"
178
+ end
179
+
180
+ tests.zip(documents).map { |test, document| build_result(test, document) }
181
+ rescue JSON::ParserError, ArgumentError => e
182
+ raise Ibex::Error, "(test):1:1: invalid grammar test process result: #{e.message}"
183
+ end
184
+
185
+ # @rbs (IR::grammar_test test, untyped document) -> Result
186
+ def build_result(test, document)
187
+ unless document.is_a?(Hash) && %w[accept reject error].include?(document["actual"])
188
+ raise Ibex::Error, "(test):1:1: grammar test process returned an invalid case result"
189
+ end
190
+
191
+ production_ids = document["production_ids"]
192
+ production_count = @automaton.grammar.productions.length
193
+ unless production_ids.is_a?(Array) &&
194
+ production_ids.all? { |id| id.is_a?(Integer) && id.between?(0, production_count - 1) }
195
+ raise Ibex::Error, "(test):1:1: grammar test process returned invalid production coverage"
196
+ end
197
+
198
+ Result.new(
199
+ expectation: test[:expectation], actual: document.fetch("actual").to_sym,
200
+ error_class: document["error_class"], error_message: document["error_message"],
201
+ location: test[:loc], production_ids: production_ids.freeze
202
+ ).freeze
203
+ end
204
+ end
205
+ end
206
+ end
@@ -74,17 +74,33 @@ module Ibex
74
74
  attr_reader :grammar_digest #: String
75
75
  attr_reader :grammar #: Grammar
76
76
  attr_reader :states #: Array[AutomatonState]
77
+ attr_reader :entry_states #: Hash[String, Integer]
77
78
  attr_reader :conflict_summary #: conflict_summary
78
79
  attr_reader :schema_version #: Integer
79
80
 
80
81
  # @rbs (grammar: Grammar, states: Array[AutomatonState], conflict_summary: conflict_summary,
81
- # ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer) -> void
82
+ # ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer,
83
+ # ?entry_states: Hash[String, Integer]?) -> void
82
84
  def initialize(grammar:, states:, conflict_summary:, algorithm: "lalr1", grammar_digest: nil,
83
- schema_version: SCHEMA_VERSION)
85
+ schema_version: SCHEMA_VERSION, entry_states: nil)
86
+ unless SUPPORTED_SCHEMA_VERSIONS.include?(schema_version)
87
+ raise Ibex::Error, "unsupported automaton schema_version #{schema_version.inspect}"
88
+ end
89
+
90
+ grammar = Migration.to_v2(grammar) if schema_version >= 2 && grammar.schema_version != schema_version
91
+ raise Ibex::Error, "automaton migration did not produce a grammar" unless grammar.is_a?(Grammar)
92
+ unless grammar.schema_version == schema_version
93
+ raise Ibex::Error,
94
+ "automaton schema_version #{schema_version} requires Grammar IR v#{schema_version}, " \
95
+ "got v#{grammar.schema_version}"
96
+ end
97
+
84
98
  @algorithm = algorithm.freeze
85
99
  @grammar = grammar
86
100
  @grammar_digest = (grammar_digest || digest_for(grammar)).freeze
87
101
  @states = states.freeze
102
+ @entry_states = IR.deep_freeze(entry_states || { grammar.start => 0 })
103
+ validate_entry_states
88
104
  @conflict_summary = IR.deep_freeze(conflict_summary)
89
105
  @schema_version = schema_version
90
106
  freeze
@@ -92,13 +108,30 @@ module Ibex
92
108
 
93
109
  # @rbs () -> Hash[Symbol, untyped]
94
110
  def to_h
95
- { ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
96
- grammar_digest: @grammar_digest, grammar: @grammar.to_h,
97
- states: @states.map { |state| state.to_h(@grammar) }, conflict_summary: @conflict_summary }
111
+ value = { ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
112
+ grammar_digest: @grammar_digest, grammar: @grammar.to_h,
113
+ states: @states.map { |state| state.to_h(@grammar) },
114
+ conflict_summary: @conflict_summary } #: Hash[Symbol, untyped]
115
+ value[:entry_states] = @entry_states unless @entry_states == { @grammar.start => 0 }
116
+ value
98
117
  end
99
118
 
100
119
  private
101
120
 
121
+ # @rbs () -> void
122
+ def validate_entry_states
123
+ ordered = @grammar.starts.select { |name| @entry_states.key?(name) }
124
+ unless @entry_states.any? && @entry_states.keys == ordered
125
+ raise Ibex::Error, "(ir):1:1: automaton entry states must be an ordered subset of grammar starts"
126
+ end
127
+
128
+ @entry_states.each do |name, state|
129
+ next if state.between?(0, @states.length - 1)
130
+
131
+ raise Ibex::Error, "(ir):1:1: entry #{name} references missing state #{state}"
132
+ end
133
+ end
134
+
102
135
  # @rbs (Grammar grammar) -> String
103
136
  def digest_for(grammar)
104
137
  require "digest"