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
@@ -5,11 +5,14 @@ copy racc's internal table arrays, internal method names, native runtime, or gen
5
5
 
6
6
  ## Typical migration
7
7
 
8
- 1. Run `ibex -o parser.rb grammar.y` in place of `racc -o parser.rb grammar.y`.
9
- 2. Change the generated-file runtime dependency from deployment packaging only; application calls to `do_parse`, `yyparse`,
8
+ 1. Run `ibex migrate-check grammar.y` and resolve any errors. Use `--format=json` for CI.
9
+ 2. Generate a reviewable parity harness with `ibex migrate-harness -o migration_harness.rb grammar.y`, add explicit token
10
+ cases, and run it inside an isolation boundary appropriate for the grammar code.
11
+ 3. Run `ibex -o parser.rb grammar.y` in place of `racc -o parser.rb grammar.y`.
12
+ 4. Change the generated-file runtime dependency from deployment packaging only; application calls to `do_parse`, `yyparse`,
10
13
  `next_token`, `on_error`, `token_to_str`, `yyerror`, `yyerrok`, and `yyaccept` remain the same.
11
- 3. Use `-E` if the generated parser must be a single file with no installed Ibex gem.
12
- 4. Keep the default `--mode=racc` until intentionally adopting EBNF or names. Extended grammars can make that choice locally by
14
+ 5. Use `-E` if the generated parser must be a single file with no installed Ibex gem.
15
+ 6. Keep the default `--mode=default` until intentionally adopting EBNF or names. Extended grammars can make that choice locally by
13
16
  placing `pragma extended` immediately after their class header instead of requiring `--mode=extended` at each invocation.
14
17
 
15
18
  ## CLI mapping
@@ -23,6 +26,7 @@ copy racc's internal table arrays, internal method names, native runtime, or gen
23
26
  | `-E`, `--embedded` | Embed the Pure Ruby runtime |
24
27
  | `-F`, `--frozen` | Accepted; Ibex always emits frozen-string magic comments |
25
28
  | `--rbs[=FILE]` | Ibex extension; emit a generated parser signature |
29
+ | `--action-source[=FILE]` | Ibex extension; emit a non-executable Steep shadow of semantic methods |
26
30
  | `--warnings=all,error` | Ibex extension; display or promote structured grammar diagnostics |
27
31
  | `--line-convert-all`, `-l` | Map header/inner/footer too, or disable all source mapping |
28
32
  | `-a` | Generate methods for implicit actions |
@@ -34,6 +38,32 @@ copy racc's internal table arrays, internal method names, native runtime, or gen
34
38
  Ibex defaults to `<input>.rb`; racc 1.8.1 was observed to default to `<input>.tab.rb`. Use `-o` for portable scripts.
35
39
  By default, semantic actions and `inner` methods report grammar-file lines while `header` and `footer` retain generated-file
36
40
  lines. `--line-convert-all` maps every user-code section; `-l` maps none. The mapping is retained through IR JSON resumption.
41
+ For static action checking, combine `--rbs --action-source`; configure the generated `.rbs` and `.actions.rb` in the
42
+ application's Steep target, and run Steep separately. The shadow is check-only input and must not replace or be required by the
43
+ runtime parser.
44
+
45
+ `migrate-check` never executes semantic actions or `header`/`inner`/`footer` code. `migrate-harness` also only writes source. The
46
+ generated harness is the explicit execution step: after reviewed cases are added, it invokes both generators and executes both
47
+ generated parsers in bounded child processes. This is not a sandbox; use a container or VM for untrusted grammar code. See
48
+ [ADR 0012](decisions/0012-bounded-nonexecuting-analysis.md).
49
+
50
+ ## Compatibility baseline
51
+
52
+ The compatibility claims above were checked on 2026-07-22 against racc 1.8.1 using only its public documentation, `racc --help`,
53
+ and black-box execution. Ibex does not inspect racc implementation files or generated source. Self-authored probes compare
54
+ observable results for arithmetic precedence, empty rules, string tokens, `convert`, `no_result_var`, inline actions,
55
+ dangling-else `expect`, error recovery, source-line conversion, and a generated 500-production grammar.
56
+
57
+ Precedence-resolved conflicts remain visible in Automaton IR but are excluded from the CLI conflict count and `expect`. Error
58
+ recovery probes compare result values and the public `on_error` arguments. These tests skip when the `racc` executable is absent.
59
+
60
+ Application-defined parser initializers do not have to call `super`; the runtime completes its isolated session state on first
61
+ use while retaining application-owned instance variables. During parsing, the historical `@vstack` and `@racc_vstack` names
62
+ both refer to the live semantic-value stack for read compatibility. They are internal aliases: applications may inspect them
63
+ while tokenizing but must not mutate, replace, or retain them across parser sessions.
64
+
65
+ An unqualified `ParseError` in a parser method or semantic action resolves through `Runtime::Parser`, so application actions can
66
+ keep their existing explicit rejection path. Structured failures remain available as `Ibex::Runtime::ParseError`.
37
67
 
38
68
  ## Known differences
39
69
 
@@ -43,5 +73,3 @@ lines. `--line-convert-all` maps every user-code section; `-l` maps none. The ma
43
73
  unknown lookahead through `on_error` first, then recovers if the callback returns. Declared invalid tokens match in the
44
74
  black-box recovery probe.
45
75
  - `require "racc/parser"` replacement and previously generated racc parser table compatibility are out of scope.
46
-
47
- See [compatibility observations](compat-notes.md) for the tested version and probe set.
@@ -0,0 +1,163 @@
1
+ # v1.0 readiness report
2
+
3
+ This is the outcome-based v1.0 decision required by the project design. It
4
+ records measurements taken on 2026-07-27 with Ruby 4.0.0 on
5
+ `arm64-darwin24`. Passing the implementation checklist is not sufficient for a
6
+ stable release.
7
+
8
+ ## Decision
9
+
10
+ **HOLD v1.0.** Compatibility behavior, scale construction, semantic-value
11
+ signatures, and the repository compatibility suite have evidence. The external
12
+ generator/runtime performance target is not met, and the published ten-case
13
+ error comparison has not received independent third-party review.
14
+
15
+ The feature freeze remains in force while those two release gates are open.
16
+ The format-v6 batch Red/Green CST has been selected as part of the initial
17
+ Stable v1 API. Its syntax-only incremental layer remains Experimental. Other Preview and
18
+ Experimental features may continue to ship in prereleases without promotion.
19
+
20
+ ## KPI result
21
+
22
+ | KPI | Result | Evidence |
23
+ |---|---|---|
24
+ | Three public-gem migrations | Pass with documented adapters | Namae, BCDice, and Nokogiri behavior suites below |
25
+ | Hundreds-of-productions scale | Pass | 501 productions, 503 states, 125.008 ms average complete build |
26
+ | Ten-case error UX comparison | Partial | 10/10 snapshots and 8/10 useful repairs are public; independent review is missing |
27
+ | Generator and parser performance at least racc | Fail | Reuse medians are at parity, but only Nokogiri's interval passes; all cold-generation and new-instance rows fail |
28
+ | Semantic-value RBS and typed ratchet | Pass | Generated reduction signatures include declared RHS/LHS types; whole-library Steep is 89.6% typed |
29
+ | Compatibility suite unbeaten | Pass at the measured revision | Current black-box, self-host, IR, property, and runtime suites are green |
30
+
31
+ ## Public-gem migration evidence
32
+
33
+ Only public grammar files, public commands, and observable test results were
34
+ used. Neither racc implementation files nor generated source were inspected.
35
+ Each repository was cloned at the recorded revision:
36
+
37
+ | Project | Revision and grammar | Static check | Behavior result |
38
+ |---|---|---|---|
39
+ | Namae | [`d33875a`](https://github.com/berkmancenter/namae/commit/d33875aaf1fc420a8dfe946a3b29cc3e19710061), `lib/namae/parser.y` | compatible | 151 RSpec examples, 85 Cucumber scenarios, 198 steps |
40
+ | BCDice | [`21b4a03`](https://github.com/bcdice/BCDice/commit/21b4a03789bf2080ad41aaf31299b609ee7bda86), `lib/bcdice/command/parser.y` | compatible; runtime-constant warning | 25 tests, 103 assertions |
41
+ | Nokogiri | [`04a4c29`](https://github.com/sparklemotion/nokogiri/commit/04a4c29c6a605ad40a78f4ce343ced0832a1805c), `lib/nokogiri/css/parser.y` | compatible; runtime-constant warning | 6 tests, 14 assertions |
42
+
43
+ Namae generated and ran as one embedded file. The other two grammars contain a
44
+ user header whose superclass is named `Racc::Parser`. Their PoCs preload this
45
+ explicit migration adapter before loading the generated parser:
46
+
47
+ ```ruby
48
+ require "ibex/runtime"
49
+
50
+ module Racc
51
+ Parser = Ibex::Runtime::Parser
52
+ end
53
+ ```
54
+
55
+ This is the adapter recommended by `ibex migrate-check`; it does not load or
56
+ execute the racc runtime. The PoCs uncovered and fixed three compatibility
57
+ gaps: an omitted `end` before a user-code section, an application initializer
58
+ that omits `super` plus historical value-stack reads, and an unqualified
59
+ `ParseError`.
60
+
61
+ ## Formal Pure Ruby performance comparison
62
+
63
+ The formal artifact was collected from clean revision `3fda78c` with ten
64
+ alternating isolated runs per implementation, 50 warm-up workloads, 250
65
+ measured workloads, and 10,000 bootstrap samples. Each workload parses the
66
+ five fixed public inputs. Ruby 4.0.0 ran with YJIT disabled; Racc 1.8.1 was
67
+ forced to its Ruby backend and every worker verified the selected runtime.
68
+ Ratios below are Ibex divided by Racc. Bold values meet the gate, whose timing
69
+ and allocation metrics require the bootstrap interval's upper bound to be no
70
+ greater than 1.0.
71
+
72
+ | Project | Cold generation time | Reuse time | Reuse allocations | New-instance time | New-instance allocations | Generated bytes |
73
+ |---|---:|---:|---:|---:|---:|---:|
74
+ | Namae | 1.194x | 0.994x | **0.849x** | 1.185x | 1.208x | **0.862x** |
75
+ | BCDice command | 1.204x | 0.995x | **0.666x** | 1.258x | 1.129x | **0.981x** |
76
+ | Nokogiri CSS | 1.187x | **0.953x** | **0.662x** | 1.167x | 1.075x | **0.937x** |
77
+
78
+ Result values and ordered result sequences are equivalent in every runtime
79
+ row. The measured parser-time medians in milliseconds per parse were
80
+ 0.038783/0.039015 (Ibex/Racc) for Namae reuse,
81
+ 0.019542/0.019646 for BCDice reuse, and
82
+ 0.024074/0.025269 for Nokogiri reuse. The corresponding new-instance medians
83
+ were 0.047493/0.040076, 0.025937/0.020618, and 0.031041/0.026609.
84
+
85
+ The remaining runtime gap is isolated to parser-instance construction and the
86
+ per-instance mutation tracker; the reused hot loop's point estimates are at
87
+ practical parity, although Namae and BCDice still need narrower passing
88
+ intervals. Cold generation remains 18.7–20.4% slower and is dominated by
89
+ process/load, frontend, and code-generation setup. These are evidence for the
90
+ release decision, not portable scores.
91
+
92
+ ## Superseded native-backend diagnostic
93
+
94
+ The previous artifact was collected from clean revision `53aa4ab` with ten
95
+ alternating isolated runs per implementation, 50 warm-up workloads, 250
96
+ measured workloads, and 10,000 bootstrap samples. Each workload parses the
97
+ five fixed public inputs. Ruby 4.0.0 ran with YJIT disabled; racc 1.8.1 was
98
+ verified to use its native backend. The formal release contract now compares
99
+ the two Pure Ruby runtimes, so these stricter native-backend measurements are
100
+ retained only as diagnostic context and do not decide the KPI. Ratios below
101
+ are Ibex divided by racc.
102
+
103
+ | Project | Cold generation time | Reuse time | Reuse allocations | New-instance time | New-instance allocations | Generated bytes |
104
+ |---|---:|---:|---:|---:|---:|---:|
105
+ | Namae | 1.210x | 1.177x | **0.970x** | 1.408x | 1.376x | **0.862x** |
106
+ | BCDice command | 1.211x | 1.943x | **0.852x** | 2.445x | 1.430x | **0.981x** |
107
+ | Nokogiri CSS | 1.236x | 1.562x | **0.881x** | 1.993x | 1.418x | **0.937x** |
108
+
109
+ Result values and ordered result sequences are equivalent in every runtime
110
+ row. The measured parser-time medians in milliseconds per parse were
111
+ 0.038938/0.033094 (Ibex/racc) for Namae reuse,
112
+ 0.020118/0.010354 for BCDice reuse, and
113
+ 0.024413/0.015626 for Nokogiri reuse. The corresponding new-instance medians
114
+ were 0.046953/0.033340, 0.026933/0.011016, and 0.030744/0.015426.
115
+
116
+ Profiles attribute the remaining reuse gap to the Pure Ruby direct LR loop
117
+ after lexer and semantic-action costs. New-instance rows additionally include
118
+ the per-instance tracker that preserves runtime hook mutation semantics.
119
+ Cold-generation profiles are dominated by process/load cost plus frontend and
120
+ code-generation work. These are diagnostic optimization evidence, not
121
+ portable scores or formal release evidence.
122
+
123
+ ## Scale evidence
124
+
125
+ Run:
126
+
127
+ ```sh
128
+ benchmark/scale.rb --rules 500 --iterations 3
129
+ ```
130
+
131
+ The reproducible synthetic chain shares its grammar shape with the 500-rule
132
+ black-box compatibility test. The measured report contains 501 productions,
133
+ 503 direct-LALR construction states, 503 final states, no conflicts, and
134
+ 41,332 bytes of compact generated Ruby. Three complete parse-to-codegen builds
135
+ averaged 125.008 ms with a 122.742–127.002 ms range. The Automaton IR digest
136
+ was `fe0e2f0c9bf16e7aa22a7ebcea40b229fedbb573f83af37596b76e0b989c3338`.
137
+
138
+ This synthetic result establishes the requested scale bound. The 139-production
139
+ representative grammar remains the realistic workload, so neither result is
140
+ presented as a substitute for an application-specific benchmark.
141
+
142
+ ## Error UX and type evidence
143
+
144
+ [`error-ux.md`](error-ux.md) and its versioned JSON publish the same ten
145
+ malformed JSON inputs against the public racc callback. Eight selected repairs
146
+ were assessed useful. A maintainer assessment is not independent review; at
147
+ least one external reviewer must record a review of the cases and usefulness
148
+ labels before this KPI passes.
149
+
150
+ The current whole-library `steep stats` result is 18,115 typed calls and 2,097
151
+ untyped calls out of 20,212, or 89.6% typed. Generated parser RBS refines
152
+ declared terminal, nonterminal, RHS tuple, and reduction result types. Untyped
153
+ values remain explicit at undeclared grammar symbols, decoded JSON, dynamic
154
+ table cells, and opaque application Ruby boundaries.
155
+
156
+ ## Actions required to release v1.0
157
+
158
+ 1. Optimize cold generation and parser-instance construction, then repeat the
159
+ formal Pure Ruby measurements until no row is slower than Racc.
160
+ 2. Obtain and link an independent review of the ten error cases and usefulness
161
+ judgments; revise the versioned assessment if the review disagrees.
162
+ 3. Re-run every compatibility, IR, type, benchmark, and site gate on the exact
163
+ release revision.
data/docs/stability.md ADDED
@@ -0,0 +1,124 @@
1
+ # Stability, compatibility, and deprecation
2
+
3
+ Ibex separates support level from activation. Opt-in controls do not determine
4
+ maturity: a documented opt-in API can be Stable, Preview, or Experimental.
5
+
6
+ ## Maturity ladder
7
+
8
+ | Level | Activation | Guarantee |
9
+ |---|---|---|
10
+ | Stable | Default compatible mode or a documented stable API | Semantic versioning; compatible-mode behavior remains unchanged |
11
+ | Preview | Explicit pragma, mode, command, or algorithm | Breaking changes require notice one minor release in advance |
12
+ | Experimental | Explicit policy/object or research entry point | May change without notice; budgets and failure modes are part of the experiment |
13
+
14
+ After v1.0, promotion requires representative use in a shadow or gallery
15
+ grammar, feature-specific invariant/property tests, two released versions
16
+ without a specification change, dependent-tool support where applicable, and
17
+ complete public documentation. For the initial v1.0 contract, accepted ADRs,
18
+ versioned benchmark evidence, invariant/property coverage, dependent-tool
19
+ support where applicable, and complete public documentation replace the
20
+ impossible two-prior-release requirement. Only Stable features may be adopted
21
+ by the production self-hosted grammar.
22
+
23
+ ## v1 inventory
24
+
25
+ Stable:
26
+
27
+ - racc-compatible grammar input and generated `do_parse`/`yyparse` runtime;
28
+ - default direct LALR construction, parser tables, recovery callbacks,
29
+ observation events, resource limits, migration checks, and bounded
30
+ counterexample/ambiguity analysis;
31
+ - versioned core Grammar IR, Automaton IR, Lexer IR, table formats, report
32
+ schemas, and their validators;
33
+ - format-v6 Red/Green batch CST parsing, typed syntax views, persistent editing
34
+ and diffing, and the closed `ibex_cst` schema v1 serialization contract.
35
+
36
+ Preview:
37
+
38
+ - `pragma extended` / `--mode=extended`, including EBNF groups, parameterized
39
+ and inline rules, middle actions, multiple entries, canonical imports,
40
+ generated lexers, semantic locations/types, AST generation, grammar tests,
41
+ and documentation tooling;
42
+ - the conservative `--algorithm=ielr` backend;
43
+ - LSP, watch, debug, coverage, browser playground, and static action-shadow
44
+ integration.
45
+
46
+ Experimental:
47
+
48
+ - opt-in bounded insertion/deletion/replacement repair through
49
+ `Runtime::RepairPolicy`;
50
+ - syntax-only incremental CST sessions and conservative Blender subtree reuse.
51
+
52
+ The batch CST contract is selected for the initial v1 API under the
53
+ initial-major evidence rule above. The remaining Preview features have not
54
+ completed the normal two-release field period. Repair passed its usefulness
55
+ spike but remains Experimental because two of ten baseline plans were not
56
+ useful and inserted semantic values may be nil.
57
+
58
+ ## Research decisions
59
+
60
+ - IELR passed its correctness/state-bound spike and remains preview while it
61
+ gains field experience. Direct LALR remains the default.
62
+ - Bounded repair passed SP-4 with 8/10 useful plans and remains experimental.
63
+ - GLR and `%dprec`/`%merge` did not enter the product: no delayed-action spike
64
+ met the ≤5% deterministic-overhead and ambiguity-policy gates.
65
+ - Syntax-only incremental parsing entered as experimental after fixed-seed
66
+ structural edits matched fresh Green trees and the representative benchmark
67
+ measured 1.04–2.83x over Stage A and 1.66–4.48x over fresh syntax sessions.
68
+ It remains experimental until it completes the two-release field period.
69
+
70
+ The supported alternatives are canonical LR(1) or IELR for LALR inadequacy,
71
+ bounded ambiguity/counterexample analysis for ambiguous grammars, and
72
+ `--watch` with full deterministic reparsing for editing workflows.
73
+
74
+ ## Core IR freeze
75
+
76
+ The required fields, meanings, identity rules, ordering, and validation
77
+ semantics of the published core Grammar IR and Automaton IR versions are
78
+ frozen. Existing version-1 and version-2 documents retain byte-stable
79
+ round-trips and one-generation read compatibility.
80
+
81
+ Additive optional core fields require a minor release. Meaning changes,
82
+ required-field changes, or removals require a new major schema version and
83
+ continued reading of the immediately preceding major version.
84
+
85
+ The `x-` namespace is reserved for future experimental data and is not frozen.
86
+ Current closed schemas intentionally reject unknown fields and do not emit
87
+ `x-` data. Introducing an experimental envelope therefore requires a new
88
+ additive schema version; it cannot silently place fields into an existing
89
+ closed document. Promotion moves reviewed data into a documented core field in
90
+ a later schema version.
91
+
92
+ ## Compatibility policy
93
+
94
+ Compatible mode is the permanent default. Opt-in extensions, exact lookahead
95
+ defaults, repair, and research algorithms do not silently replace compatible
96
+ behavior. Public migration evidence uses public commands and black-box
97
+ behavior; implementation and generated-source layouts are not compatibility
98
+ surfaces.
99
+
100
+ An undeclared invalid token intentionally calls `on_error` before ordinary yacc
101
+ recovery. This is the documented recommended behavior and is not changed by
102
+ the freeze.
103
+
104
+ Parser-table formats v1 through v6 remain readable for non-CST parsers. CST
105
+ tables must use the current format v6 structured metadata; older CST tables
106
+ and the boolean `cst: true` shape fail before token consumption with a
107
+ regeneration instruction. Format v6 is the only writer and does not change
108
+ Grammar IR v2. The closed `ibex_cst` schema v1 is a versioned interchange
109
+ contract. Additive meaning requires a new schema version; readers do not
110
+ accept unknown fields.
111
+
112
+ ## Deprecation policy
113
+
114
+ After v1.0, a Stable API or syntax first emits a migration warning for at least
115
+ two minor releases. The release notes and documentation must name the first
116
+ warning release, replacement, migration command or procedure, and earliest
117
+ removal release. Automated migration is supplied when practical; policy does
118
+ not promise a command that the product does not provide. Preview features
119
+ receive at least one minor release of notice, and Experimental features may
120
+ change without notice.
121
+
122
+ The pre-v1 mixed semantic/syntax CST was a Preview contract and is removed
123
+ while selecting the initial stable API. Its parser tables are rejected with a
124
+ regeneration instruction. There are no Stable removals scheduled.
@@ -0,0 +1,60 @@
1
+ # Ibex examples
2
+
3
+ These examples pair an Ibex grammar with either the declarative lexer DSL or a
4
+ small handwritten lexer. Generate any example from the repository
5
+ root, then run the generated file:
6
+
7
+ ```sh
8
+ bundle exec ruby -Ilib exe/ibex examples/calculator.y
9
+ bundle exec ruby -Ilib examples/calculator.rb "2 + 3 * (4 - 1)"
10
+ ```
11
+
12
+ The generated `.rb` file is disposable. The `.y` file is the maintained
13
+ source.
14
+
15
+ Each grammar also keeps accept/reject source examples beside its rules. Run all
16
+ of them without generating repository files. The task also requires 100%
17
+ production coverage from the declared cases:
18
+
19
+ ```sh
20
+ bundle exec rake grammar:test
21
+ ```
22
+
23
+ ## Included grammars
24
+
25
+ - `calculator.y` evaluates integer arithmetic with parentheses.
26
+ - `csv.y` parses rows and quoted fields with a generated lexer.
27
+ - `json.y` parses JSON values into Ruby Hashes, Arrays, Strings, numbers,
28
+ booleans, and `nil`. It demonstrates the generated lexer, including conversion
29
+ actions and punctuation emission.
30
+ - `ini.y` parses sections and key/value entries into a nested Hash.
31
+ - `tiny_language.y` parses assignments, arithmetic, and `print` statements,
32
+ then executes the resulting small AST.
33
+
34
+ The JSON, INI, and tiny-language runners read standard input:
35
+
36
+ ```sh
37
+ printf '%s\n' '{"name":"Ibex","values":[1,true,null]}' |
38
+ bundle exec ruby -Ilib examples/json.rb
39
+ ```
40
+
41
+ ## Lexer integration patterns
42
+
43
+ The JSON example declares `lexer ... end`; generated `parse` accepts String,
44
+ IO, and Fiber input. Rules are anchored, choose the longest match, and use
45
+ declaration order for ties.
46
+
47
+ Handwritten lexers remain useful when an application already owns tokenization.
48
+ They implement `next_token`, returning `[token, semantic_value]`; `false` or
49
+ `nil` marks EOF. The remaining examples demonstrate two `StringScanner`
50
+ patterns:
51
+
52
+ 1. calculator and tiny language scan the source incrementally and
53
+ return one token per `next_token` call;
54
+ 2. INI tokenizes line-oriented records into a small queue before parsing.
55
+
56
+ Quoted grammar terminals such as `'+'` are returned as Strings. Declared bare
57
+ tokens such as `NUMBER` are returned as Symbols. A production lexer can retain
58
+ offset or line information alongside its own semantic values and use that
59
+ information in application diagnostics; these examples keep the contract
60
+ small enough to read in one file.
@@ -0,0 +1,44 @@
1
+ class Examples::CalculatorParser
2
+ pragma extended
3
+ token NUMBER
4
+ %test accept "1+2*3"
5
+ %test accept "8-3"
6
+ %test accept "8/2"
7
+ %test accept "(1)"
8
+ %test reject "1+"
9
+ rule
10
+ expression : expression '+' term { result = val[0] + val[2] }
11
+ | expression '-' term { result = val[0] - val[2] }
12
+ | term { result = val[0] }
13
+ term : term '*' factor { result = val[0] * val[2] }
14
+ | term '/' factor { result = val[0] / val[2] }
15
+ | factor { result = val[0] }
16
+ factor : NUMBER { result = val[0] }
17
+ | '(' expression ')' { result = val[1] }
18
+ end
19
+ ---- header
20
+ require "strscan"
21
+ ---- inner
22
+ def parse(source)
23
+ @scanner = StringScanner.new(source)
24
+ do_parse
25
+ end
26
+
27
+ def next_token
28
+ @scanner.skip(/\s+/)
29
+ return false if @scanner.eos?
30
+
31
+ if (number = @scanner.scan(/\d+/))
32
+ [:NUMBER, Integer(number, 10)]
33
+ elsif (operator = @scanner.scan(/[()+*\/-]/))
34
+ [operator, nil]
35
+ else
36
+ raise ArgumentError, "unexpected calculator input at offset #{@scanner.pos}"
37
+ end
38
+ end
39
+ ---- footer
40
+ if $PROGRAM_NAME == __FILE__
41
+ abort "usage: ruby calculator.rb EXPRESSION" if ARGV.empty?
42
+
43
+ puts Examples::CalculatorParser.new.parse(ARGV.join(" "))
44
+ end
data/examples/csv.y ADDED
@@ -0,0 +1,32 @@
1
+ class Examples::CSVParser
2
+ pragma extended
3
+ token FIELD NEWLINE
4
+ %test accept "name,age\n\"Doe, Jane\",36\n"
5
+ %test reject "name,age"
6
+ lexer
7
+ FIELD /"(?:[^"]|"")*"|[^,\r\n]+/ { |source| decode_csv_field(source) }
8
+ NEWLINE /\r\n|\n|\r/
9
+ on /,/ { |source| emit source, nil }
10
+ end
11
+ rule
12
+ document : rows { result = val[0] }
13
+ rows : rows row { result = val[0] + [val[1]] }
14
+ | row { result = [val[0]] }
15
+ row : fields NEWLINE { result = val[0] }
16
+ fields : fields ',' field { result = val[0] + [val[2]] }
17
+ | field { result = [val[0]] }
18
+ field : FIELD { result = val[0] }
19
+ end
20
+ ---- inner
21
+ def parse(source, file: "(csv)") = super
22
+
23
+ def decode_csv_field(source)
24
+ return source unless source.start_with?('"')
25
+
26
+ source[1...-1].gsub('""', '"')
27
+ end
28
+ ---- footer
29
+ if $PROGRAM_NAME == __FILE__
30
+ require "json"
31
+ puts JSON.generate(Examples::CSVParser.new.parse(ARGF.read))
32
+ end
data/examples/ini.y ADDED
@@ -0,0 +1,70 @@
1
+ class Examples::INIParser
2
+ pragma extended
3
+ token SECTION KEY VALUE NEWLINE
4
+ %test accept "[app]\nname=ibex\n"
5
+ %test accept "\n"
6
+ rule
7
+ document : lines { result = build_document(val[0]) }
8
+ lines : lines line { result = val[1] ? val[0] + [val[1]] : val[0] }
9
+ | { result = [] }
10
+ line : SECTION NEWLINE { result = [:section, val[0]] }
11
+ | KEY '=' VALUE NEWLINE { result = [:entry, val[0], val[2]] }
12
+ | NEWLINE { result = nil }
13
+ end
14
+ ---- header
15
+ require "strscan"
16
+ ---- inner
17
+ def parse(source)
18
+ @tokens = tokenize_ini(source)
19
+ do_parse
20
+ end
21
+
22
+ def next_token
23
+ @tokens.shift || false
24
+ end
25
+
26
+ def tokenize_ini(source)
27
+ source.lines.flat_map.with_index(1) do |line, line_number|
28
+ scanner = StringScanner.new(line.chomp)
29
+ scanner.skip(/\s*/)
30
+ if scanner.eos? || scanner.peek(1).match?(/[;#]/)
31
+ [[:NEWLINE, nil]]
32
+ elsif scanner.scan(/\[/)
33
+ name = scanner.scan(/[^\]]+/)&.strip
34
+ closer = scanner.scan(/\]/)
35
+ scanner.skip(/\s*/)
36
+ invalid_ini_line!(line_number) unless name && !name.empty? && closer && scanner.eos?
37
+ [[:SECTION, name], [:NEWLINE, nil]]
38
+ else
39
+ key = scanner.scan(/[A-Za-z0-9_.-]+/)
40
+ scanner.skip(/\s*/)
41
+ separator = scanner.scan(/=/)
42
+ scanner.skip(/\s*/)
43
+ value = scanner.rest.strip
44
+ invalid_ini_line!(line_number) unless key && separator
45
+ [[:KEY, key], ["=", nil], [:VALUE, value], [:NEWLINE, nil]]
46
+ end
47
+ end
48
+ end
49
+
50
+ def invalid_ini_line!(line_number)
51
+ raise ArgumentError, "invalid INI input on line #{line_number}"
52
+ end
53
+
54
+ def build_document(records)
55
+ document = {}
56
+ current = document
57
+ records.each do |record|
58
+ if record[0] == :section
59
+ current = (document[record[1]] ||= {})
60
+ else
61
+ current[record[1]] = record[2]
62
+ end
63
+ end
64
+ document
65
+ end
66
+ ---- footer
67
+ if $PROGRAM_NAME == __FILE__
68
+ require "json"
69
+ puts JSON.generate(Examples::INIParser.new.parse(ARGF.read))
70
+ end
data/examples/json.y ADDED
@@ -0,0 +1,48 @@
1
+ class Examples::JSONParser
2
+ pragma extended
3
+ token STRING NUMBER TRUE FALSE NULL
4
+ %test accept "{\"ok\":[true,null]}"
5
+ %test accept "{\"object\":{},\"array\":[],\"string\":\"ibex\",\"number\":1,\"false\":false,\"many\":[1,2]}"
6
+ %test reject "{\"ok\":}"
7
+ lexer
8
+ skip /\s+/
9
+ STRING /"(?:\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})|[^"\\])*"/ { |source| decode_json_string(source) }
10
+ NUMBER /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/ {
11
+ |source| source.match?(/[.eE]/) ? Float(source) : Integer(source, 10)
12
+ }
13
+ TRUE /true/ { true }
14
+ FALSE /false/ { false }
15
+ NULL /null/ { nil }
16
+ on /[{}\[\],:]/ { |source| emit source, nil }
17
+ end
18
+ rule
19
+ document : value { result = val[0] }
20
+ value : object { result = val[0] }
21
+ | array { result = val[0] }
22
+ | STRING { result = val[0] }
23
+ | NUMBER { result = val[0] }
24
+ | TRUE { result = true }
25
+ | FALSE { result = false }
26
+ | NULL { result = nil }
27
+ object : '{' '}' { result = {} }
28
+ | '{' members '}' { result = val[1] }
29
+ members : pair { result = { val[0][0] => val[0][1] } }
30
+ | members ',' pair { result = val[0].merge(val[2][0] => val[2][1]) }
31
+ pair : STRING ':' value { result = [val[0], val[2]] }
32
+ array : '[' ']' { result = [] }
33
+ | '[' elements ']' { result = val[1] }
34
+ elements : value { result = [val[0]] }
35
+ | elements ',' value { result = val[0] + [val[2]] }
36
+ end
37
+ ---- header
38
+ require "json"
39
+ ---- inner
40
+ def parse(source, file: "(json)") = super
41
+
42
+ def decode_json_string(source)
43
+ JSON.parse(source)
44
+ end
45
+ ---- footer
46
+ if $PROGRAM_NAME == __FILE__
47
+ puts JSON.generate(Examples::JSONParser.new.parse(ARGF.read))
48
+ end