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,288 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "action_locations"
5
+
6
+ module Ibex
7
+ module Codegen
8
+ # Selects the smallest generated semantic-action ABI that preserves the
9
+ # action's declared inputs.
10
+ # rubocop:disable Metrics/ModuleLength -- ABI proof and source rewriting must evolve together.
11
+ module GeneratedActionABI
12
+ LEGACY_PARAMETERS = %w[
13
+ _values
14
+ _ibex_locations
15
+ _ibex_location_stack
16
+ _ibex_location
17
+ _ibex_lookahead_location
18
+ ].freeze #: Array[String]
19
+ SIMPLE_POSITIONAL_READ = /(?<![.\w@$])val\[\s*(\d+)\s*\]/ #: Regexp
20
+ SIMPLE_POSITIONAL_UNSAFE = %r{['"`#%/\\]|<<} #: Regexp
21
+ private_constant :LEGACY_PARAMETERS
22
+ private_constant :SIMPLE_POSITIONAL_READ, :SIMPLE_POSITIONAL_UNSAFE
23
+
24
+ # Keeps semantic-action analysis local to one code-generation run.
25
+ class Cache
26
+ # @rbs @values_only: Hash[Integer, bool]
27
+ # @rbs @borrowed_values: Hash[Integer, bool]
28
+ # @rbs @positional_actions: Hash[Integer, String?]
29
+
30
+ # @rbs () -> void
31
+ def initialize
32
+ @values_only = {}
33
+ @borrowed_values = {}
34
+ @positional_actions = {}
35
+ end
36
+
37
+ # @rbs (IR::Production production) -> bool
38
+ def values_only?(production)
39
+ @values_only.fetch(production.id) do
40
+ @values_only[production.id] = GeneratedActionABI.values_only?(production)
41
+ end
42
+ end
43
+
44
+ # @rbs (IR::Production production) -> bool
45
+ def borrowed_values?(production)
46
+ @borrowed_values.fetch(production.id) do
47
+ @borrowed_values[production.id] =
48
+ GeneratedActionABI.borrowed_values?(production, analysis: self)
49
+ end
50
+ end
51
+
52
+ # @rbs (IR::Production production) -> String?
53
+ def positional_action_source(production)
54
+ @positional_actions.fetch(production.id) do
55
+ @positional_actions[production.id] =
56
+ GeneratedActionABI.positional_action_source(production, analysis: self)
57
+ end
58
+ end
59
+
60
+ # @rbs (IR::Production production) -> bool
61
+ def positional_values?(production)
62
+ !positional_action_source(production).nil?
63
+ end
64
+ end
65
+
66
+ module_function
67
+
68
+ # @rbs (IR::Production production) -> bool
69
+ def values_only?(production)
70
+ return false if production.action&.composition
71
+
72
+ action = production.action
73
+ return true unless action
74
+ return false if action.context_length.positive?
75
+
76
+ maximum = production.rhs.length
77
+ locations = ActionLocations.new(action.code, maximum: maximum, location: action.location)
78
+ !locations.references? && !references_legacy_parameter?(action.code)
79
+ end
80
+
81
+ # The direct runtime can share one reduction-values array with an action
82
+ # and a hook installed by that action only when the action cannot mutate
83
+ # or retain the array itself. Element reads and parallel assignment copy
84
+ # values out without exposing the container.
85
+ # @rbs (IR::Production production, ?analysis: singleton(GeneratedActionABI) | Cache) -> bool
86
+ def borrowed_values?(production, analysis: self)
87
+ return false unless analysis.values_only?(production)
88
+
89
+ action = production.action
90
+ return false unless action
91
+
92
+ maximum = production.rhs.length
93
+ source = ActionLocations.new(action.code, maximum: maximum, location: action.location).rewrite
94
+ return true if simple_indexed_values_action?(source)
95
+
96
+ require "ripper"
97
+ syntax = Object.const_get(:Ripper).__send__(:sexp, source)
98
+ !syntax.nil? && read_only_value_references?(syntax)
99
+ end
100
+
101
+ # Return semantic source rewritten for zero-to-four positional RHS
102
+ # arguments, or nil when the values container remains observable.
103
+ # @rbs (IR::Production production, ?analysis: singleton(GeneratedActionABI) | Cache) -> String?
104
+ def positional_action_source(production, analysis: self)
105
+ return nil unless analysis.values_only?(production)
106
+ return nil unless production.rhs.length <= 4
107
+
108
+ action = production.action
109
+ return "" unless action
110
+ return nil unless analysis.borrowed_values?(production)
111
+
112
+ parameters = Array.new(production.rhs.length) { |index| "v#{index}" }
113
+ return nil if action.named_refs.any? { |reference| parameters.include?(reference[:name]) }
114
+
115
+ maximum = production.rhs.length
116
+ source = ActionLocations.new(action.code, maximum: maximum, location: action.location).rewrite
117
+ simple = fast_positional_action_source(source, parameters)
118
+ return simple unless simple.nil?
119
+
120
+ require "ripper"
121
+ tokens = Object.const_get(:Ripper).__send__(:lex, source)
122
+ return nil unless tokens.map { |_position, _event, token, _state| token }.join == source
123
+
124
+ rewrite_positional_values(tokens, parameters)
125
+ end
126
+
127
+ # @rbs (String source, Array[String] parameters) -> String?
128
+ def fast_positional_action_source(source, parameters)
129
+ simple = simple_positional_action_source(source, parameters)
130
+ return simple unless simple.nil?
131
+
132
+ simple_indexed_positional_action_source(source, parameters)
133
+ end
134
+ private_class_method :fast_positional_action_source
135
+
136
+ # @rbs (IR::Production production) -> bool
137
+ def positional_values?(production)
138
+ !positional_action_source(production).nil?
139
+ end
140
+
141
+ # Avoid loading a Ruby parser for the overwhelmingly common generated
142
+ # action shape. This accepts only a single result assignment (or a bare
143
+ # expression), simple numeric val reads, and no further assignment.
144
+ # Anything less obvious falls back to Ripper below.
145
+ # @rbs (String source) -> bool
146
+ def simple_indexed_values_action?(source)
147
+ body = source.strip
148
+ assignment = /\Aresult\s*=\s*(.*)\z/m.match(body)
149
+ body = (assignment[1] || "").strip if assignment
150
+ return false if body.empty? || body.match?(/(?<![=!<>])=(?!=|>)/)
151
+
152
+ without_reads = body.gsub(/\bval\[\s*\d+\s*\]/, "")
153
+ without_reads != body && !without_reads.match?(/\bval\b/)
154
+ end
155
+ private_class_method :simple_indexed_values_action?
156
+
157
+ # Keep the most common identity action off the Ripper load path. The
158
+ # whole-source match excludes strings, comments, receivers, and compound
159
+ # expressions before replacing the one direct read.
160
+ # @rbs (String source, Array[String] parameters) -> String?
161
+ def simple_positional_action_source(source, parameters)
162
+ match = /\A\s*(?:result\s*=\s*)?val\[\s*(\d+)\s*\]\s*\z/.match(source)
163
+ return nil unless match
164
+
165
+ parameter = parameters[Integer(match[1] || "", 10)]
166
+ source.sub(/\bval\[\s*\d+\s*\]/, parameter) if parameter
167
+ end
168
+ private_class_method :simple_positional_action_source
169
+
170
+ # Rewrite the common string/comment/regexp-free action shape without
171
+ # loading Ripper. The borrowed-values proof has already rejected writes
172
+ # and escaping uses of the values container.
173
+ # @rbs (String source, Array[String] parameters) -> String?
174
+ def simple_indexed_positional_action_source(source, parameters)
175
+ return nil if source.match?(SIMPLE_POSITIONAL_UNSAFE)
176
+ return nil if parameters.any? { |parameter| source.match?(/\b#{Regexp.escape(parameter)}\b/) }
177
+
178
+ valid = true
179
+ rewritten = source.gsub(SIMPLE_POSITIONAL_READ) do
180
+ parameter = parameters.fetch(Integer(Regexp.last_match(1) || "", 10), nil)
181
+ valid = false unless parameter
182
+ parameter || Regexp.last_match(0)
183
+ end
184
+ return nil unless valid
185
+ return nil if rewritten.match?(/\bval\b/)
186
+
187
+ rewritten
188
+ end
189
+ private_class_method :simple_indexed_positional_action_source
190
+
191
+ # @rbs (Array[untyped] tokens, Array[String] parameters) -> String?
192
+ def rewrite_positional_values(tokens, parameters)
193
+ result = [] #: Array[String]
194
+ index = 0
195
+ while index < tokens.length
196
+ _position, event, token, _state = tokens[index]
197
+ return nil if event == :on_ident && parameters.include?(token)
198
+
199
+ unless event == :on_ident && token == "val"
200
+ result << token
201
+ index += 1
202
+ next
203
+ end
204
+
205
+ reference = positional_value_reference(tokens, index, parameters)
206
+ if reference
207
+ result << reference.fetch(0)
208
+ index = reference.fetch(1)
209
+ elsif receiver_before_value?(tokens, index) || parameters.empty?
210
+ return nil
211
+ else
212
+ # borrowed_values? has already proven that a bare `val` occurs
213
+ # only as the right-hand side of parallel assignment.
214
+ result << parameters.join(", ")
215
+ index += 1
216
+ end
217
+ end
218
+ result.join
219
+ end
220
+ private_class_method :rewrite_positional_values
221
+
222
+ # @rbs (Array[untyped] tokens, Integer index, Array[String] parameters) -> [String, Integer]?
223
+ def positional_value_reference(tokens, index, parameters)
224
+ return nil if receiver_before_value?(tokens, index)
225
+
226
+ reference = tokens.slice(index + 1, 3)
227
+ return nil unless reference
228
+ return nil unless reference.map { |entry| entry[1] } == %i[on_lbracket on_int on_rbracket]
229
+
230
+ parameter = parameters[Integer(reference.fetch(1).fetch(2), 10)]
231
+ [parameter, index + 4] if parameter
232
+ rescue ArgumentError
233
+ nil
234
+ end
235
+ private_class_method :positional_value_reference
236
+
237
+ # @rbs (Array[untyped] tokens, Integer index) -> bool
238
+ def receiver_before_value?(tokens, index)
239
+ previous = tokens.first(index).reverse.find do |entry|
240
+ !%i[on_sp on_nl on_ignored_nl on_comment].include?(entry[1])
241
+ end
242
+ return false unless previous
243
+
244
+ previous[1] == :on_period || (previous[1] == :on_op && %w[&. ::].include?(previous[2]))
245
+ end
246
+ private_class_method :receiver_before_value?
247
+
248
+ # @rbs (String source) -> bool
249
+ def references_legacy_parameter?(source)
250
+ return false unless LEGACY_PARAMETERS.any? { |parameter| source.include?(parameter) }
251
+
252
+ require "ripper"
253
+ tokens = Object.const_get(:Ripper).__send__(:lex, source.gsub(/@(?:\$|\d+)/) { |match| "_" * match.bytesize })
254
+ tokens.any? do |_position, type, token, _state|
255
+ type == :on_ident && LEGACY_PARAMETERS.include?(token)
256
+ end
257
+ end
258
+ private_class_method :references_legacy_parameter?
259
+
260
+ # @rbs (untyped node, ?untyped parent, ?Integer? child_index) -> bool
261
+ def read_only_value_references?(node, parent = nil, child_index = nil)
262
+ return true unless node.is_a?(Array)
263
+ return safe_value_reference?(parent, child_index) if value_reference?(node)
264
+
265
+ node.each_with_index.all? do |child, index|
266
+ read_only_value_references?(child, node, index)
267
+ end
268
+ end
269
+ private_class_method :read_only_value_references?
270
+
271
+ # @rbs (untyped node) -> bool
272
+ def value_reference?(node)
273
+ node[0] == :vcall && node.dig(1, 0) == :@ident && node.dig(1, 1) == "val"
274
+ end
275
+ private_class_method :value_reference?
276
+
277
+ # @rbs (untyped parent, Integer? child_index) -> bool
278
+ def safe_value_reference?(parent, child_index)
279
+ return false unless parent.is_a?(Array)
280
+
281
+ (parent[0] == :aref && child_index == 1) ||
282
+ (parent[0] == :massign && child_index == 2)
283
+ end
284
+ private_class_method :safe_value_reference?
285
+ end
286
+ # rubocop:enable Metrics/ModuleLength
287
+ end
288
+ end
@@ -11,13 +11,23 @@ module Ibex
11
11
  # Hash[Integer, String] labels) -> String
12
12
  # private def self.state_sections: (IR::Automaton automaton, IR::Grammar grammar,
13
13
  # Hash[Integer, String] labels) -> String
14
+ # private def controls: (IR::Automaton automaton) -> String
15
+ # private def self.controls: (IR::Automaton automaton) -> String
16
+ # private def styles: () -> String
17
+ # private def self.styles: () -> String
18
+ # private def interaction_script: () -> String
19
+ # private def self.interaction_script: () -> String
20
+ # private def one_hop_neighbors: (IR::Automaton automaton, Integer state_id) -> Array[Integer]
21
+ # private def self.one_hop_neighbors: (IR::Automaton automaton, Integer state_id) -> Array[Integer]
14
22
  # private def item_html: (IR::AutomatonItem item, IR::Grammar grammar, Hash[Integer, String] labels) -> String
15
23
  # private def self.item_html: (IR::AutomatonItem item, IR::Grammar grammar,
16
24
  # Hash[Integer, String] labels) -> String
17
25
  # private def rule_sections: (IR::Grammar grammar, Hash[Integer, String] labels) -> String
18
26
  # private def self.rule_sections: (IR::Grammar grammar, Hash[Integer, String] labels) -> String
19
- # private def conflict_sections: (IR::Automaton automaton) -> String
20
- # private def self.conflict_sections: (IR::Automaton automaton) -> String
27
+ # private def conflict_sections: (IR::Automaton automaton, IR::Grammar grammar,
28
+ # Hash[Integer, String] labels) -> String
29
+ # private def self.conflict_sections: (IR::Automaton automaton, IR::Grammar grammar,
30
+ # Hash[Integer, String] labels) -> String
21
31
  # private def escape: (String value) -> String
22
32
  # private def self.escape: (String value) -> String
23
33
  # private def symbol_name: (Hash[Integer, String] labels, Integer id) -> String
@@ -30,12 +40,14 @@ module Ibex
30
40
  <<~HTML
31
41
  <!doctype html>
32
42
  <html lang="en"><head><meta charset="utf-8"><title>Ibex automaton</title>
33
- <style>body{font:14px system-ui;margin:2rem;max-width:90rem}code{white-space:pre-wrap}.state{border:1px solid #bbb;padding:1rem;margin:1rem 0}.conflict{color:#a00}a{color:#075985}</style>
43
+ #{styles}
34
44
  </head><body><h1>Ibex #{escape(automaton.algorithm)} automaton</h1>
35
45
  <nav><a href="#rules">Rules</a> · <a href="#conflicts">Conflicts</a></nav>
36
- #{state_sections(automaton, grammar, labels)}
46
+ #{controls(automaton)}
47
+ <main id="states">#{state_sections(automaton, grammar, labels)}</main>
37
48
  <h2 id="rules">Rules</h2>#{rule_sections(grammar, labels)}
38
- <h2 id="conflicts">Conflicts</h2>#{conflict_sections(automaton)}
49
+ <h2 id="conflicts">Conflicts</h2>#{conflict_sections(automaton, grammar, labels)}
50
+ #{interaction_script}
39
51
  </body></html>
40
52
  HTML
41
53
  end
@@ -44,6 +56,62 @@ module Ibex
44
56
  # @rbs skip
45
57
  private
46
58
 
59
+ # @rbs skip
60
+ def styles
61
+ <<~HTML
62
+ <style>
63
+ body{font:14px system-ui;margin:2rem;max-width:90rem}code{white-space:pre-wrap}
64
+ .controls{align-items:center;display:flex;flex-wrap:wrap;gap:1rem;margin:1rem 0}
65
+ .state{border:1px solid #bbb;padding:1rem;margin:1rem 0}.state[hidden]{display:none}
66
+ .conflict-state{background:#fff7f7;border:2px solid #b91c1c}.conflict{color:#a00}a{color:#075985}
67
+ </style>
68
+ HTML
69
+ end
70
+
71
+ # @rbs skip
72
+ def interaction_script
73
+ <<~HTML
74
+ <script>
75
+ (() => {
76
+ const search = document.getElementById("state-search");
77
+ const conflictOnly = document.getElementById("conflict-only");
78
+ const neighborhood = document.getElementById("conflict-neighborhood");
79
+ const states = Array.from(document.querySelectorAll(".state"));
80
+ const update = () => {
81
+ const query = search.value.trim().toLowerCase();
82
+ const focus = neighborhood.value;
83
+ states.forEach((state) => {
84
+ const matchesText = !query || state.textContent.toLowerCase().includes(query);
85
+ const matchesConflict = !conflictOnly.checked || state.classList.contains("conflict-state");
86
+ const nearby = !focus || state.dataset.neighbors.split(" ").includes(focus);
87
+ state.hidden = !(matchesText && matchesConflict && nearby);
88
+ });
89
+ };
90
+ search.addEventListener("input", update);
91
+ conflictOnly.addEventListener("change", update);
92
+ neighborhood.addEventListener("change", update);
93
+ })();
94
+ </script>
95
+ HTML
96
+ end
97
+
98
+ # @rbs skip
99
+ def controls(automaton)
100
+ options = automaton.states.filter_map do |state|
101
+ next if state.conflicts.empty?
102
+
103
+ %(<option value="#{state.id}">State #{state.id}</option>)
104
+ end.join
105
+ <<~HTML
106
+ <div class="controls">
107
+ <label>Search states <input id="state-search" type="search" aria-controls="states"></label>
108
+ <label><input id="conflict-only" type="checkbox"> Conflicts only</label>
109
+ <label>Conflict neighborhood
110
+ <select id="conflict-neighborhood"><option value="">All states</option>#{options}</select></label>
111
+ </div>
112
+ HTML
113
+ end
114
+
47
115
  # @rbs skip
48
116
  def state_sections(automaton, grammar, labels)
49
117
  automaton.states.map do |state|
@@ -52,13 +120,25 @@ module Ibex
52
120
  "<li>#{symbol} → <a href=\"#state-#{target}\">state #{target}</a></li>"
53
121
  end.join
54
122
  items = state.items.map { |item| item_html(item, grammar, labels) }.join
123
+ class_name = state.conflicts.empty? ? "state" : "state conflict-state"
124
+ neighbors = one_hop_neighbors(automaton, state.id).join(" ")
55
125
  <<~HTML
56
- <section class="state" id="state-#{state.id}"><h2>State #{state.id}</h2>
126
+ <section class="#{class_name}" id="state-#{state.id}" data-state-id="#{state.id}" data-neighbors="#{neighbors}">
127
+ <h2>State #{state.id}</h2>
57
128
  <ul>#{transitions}</ul><ul>#{items}</ul></section>
58
129
  HTML
59
130
  end.join
60
131
  end
61
132
 
133
+ # @rbs skip
134
+ def one_hop_neighbors(automaton, state_id)
135
+ outgoing = automaton.states.fetch(state_id).transitions.values
136
+ incoming = automaton.states.filter_map do |state|
137
+ state.id if state.transitions.value?(state_id)
138
+ end
139
+ ([state_id] + outgoing + incoming).uniq.sort
140
+ end
141
+
62
142
  # @rbs skip
63
143
  def item_html(item, grammar, labels)
64
144
  return "<li><code>$accept</code></li>" if item.production.negative?
@@ -81,11 +161,13 @@ module Ibex
81
161
  end
82
162
 
83
163
  # @rbs skip
84
- def conflict_sections(automaton)
164
+ def conflict_sections(automaton, grammar, labels)
85
165
  conflicts = automaton.states.flat_map do |state|
86
166
  state.conflicts.map do |conflict|
87
167
  link = "<a href=\"#state-#{state.id}\">state #{state.id}</a>"
88
- "<li class=\"conflict\">#{link}: #{escape(conflict.inspect)}</li>"
168
+ symbol = grammar.symbol(conflict[:symbol])
169
+ displayed = symbol ? conflict.merge(symbol: symbol_name(labels, symbol.id)) : conflict
170
+ "<li class=\"conflict\">#{link}: #{escape(displayed.inspect)}</li>"
89
171
  end
90
172
  end
91
173
  conflicts.empty? ? "<p>None</p>" : "<ul>#{conflicts.join}</ul>"
@@ -100,10 +182,12 @@ module Ibex
100
182
  def symbol_name(labels, id)
101
183
  labels.fetch(id) { raise Ibex::Error, "missing grammar symbol id #{id}" }
102
184
  end
103
- module_function :state_sections, :item_html, :rule_sections, :conflict_sections, :escape, :symbol_name
185
+ module_function :styles, :interaction_script, :controls, :state_sections, :one_hop_neighbors
186
+ module_function :item_html, :rule_sections, :conflict_sections, :escape, :symbol_name
104
187
 
105
188
  class << self
106
- private :state_sections, :item_html, :rule_sections, :conflict_sections, :escape, :symbol_name
189
+ private :styles, :interaction_script, :controls, :state_sections, :one_hop_neighbors
190
+ private :item_html, :rule_sections, :conflict_sections, :escape, :symbol_name
107
191
  end
108
192
  end
109
193
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "symbol_labels"
4
+
5
+ module Ibex
6
+ module Codegen
7
+ # Renders an Automaton IR graph as a GitHub-compatible Mermaid flowchart.
8
+ module Mermaid
9
+ # @rbs (IR::Automaton automaton) -> String
10
+ def render(automaton)
11
+ labels = SymbolLabels.build(automaton.grammar)
12
+ lines = ["flowchart LR"]
13
+ automaton.states.each do |state|
14
+ lines << %( state_#{state.id}["State #{state.id}"])
15
+ state.transitions.each do |symbol_id, target|
16
+ label = escape(labels.fetch(symbol_id) { raise Ibex::Error, "missing grammar symbol id #{symbol_id}" })
17
+ lines << " state_#{state.id} -->|#{label}| state_#{target}"
18
+ end
19
+ end
20
+ conflicting = automaton.states.reject { |state| state.conflicts.empty? }
21
+ unless conflicting.empty?
22
+ lines << " classDef conflict fill:#fee2e2,stroke:#b91c1c,stroke-width:2px"
23
+ conflicting.each { |state| lines << " class state_#{state.id} conflict;" }
24
+ end
25
+ "#{lines.join("\n")}\n"
26
+ end
27
+ module_function :render
28
+
29
+ private
30
+
31
+ # @rbs (String value) -> String
32
+ def escape(value)
33
+ value.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub("|", "&#124;")
34
+ .gsub('"', "&quot;").gsub(/\s+/, " ")
35
+ end
36
+ module_function :escape
37
+
38
+ class << self
39
+ private :escape
40
+ end
41
+ end
42
+ end
43
+ end