ibex 0.2.0 → 0.4.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 (461) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +100 -272
  3. data/docs/architecture.md +187 -35
  4. data/docs/bison-import.md +103 -0
  5. data/docs/comparison-policy.md +127 -0
  6. data/docs/configuration-model.md +59 -0
  7. data/docs/configuration-report.md +28 -0
  8. data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
  9. data/docs/conflict-explanation-study.md +62 -0
  10. data/docs/construction-profiling.md +157 -0
  11. data/docs/cst-migration.md +3 -4
  12. data/docs/cst.md +24 -0
  13. data/docs/decisions/0000-template.md +24 -0
  14. data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
  15. data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
  16. data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
  17. data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
  18. data/docs/decisions/0005-contained-grammar-composition.md +34 -0
  19. data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
  20. data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
  21. data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
  22. data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
  23. data/docs/decisions/0010-committed-runtime-observation.md +34 -0
  24. data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
  25. data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
  26. data/docs/decisions/0013-transactional-generation-publication.md +37 -0
  27. data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
  28. data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
  29. data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
  30. data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
  31. data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
  32. data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
  33. data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
  34. data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
  35. data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
  36. data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
  37. data/docs/decisions/0024-direct-ielr-construction.md +42 -0
  38. data/docs/decisions/README.md +74 -0
  39. data/docs/declarative-configuration.md +274 -0
  40. data/docs/development.md +196 -2
  41. data/docs/direct-ielr-decision.md +120 -0
  42. data/docs/direct-multi-entry-decision.md +82 -0
  43. data/docs/editor-setup.md +2 -1
  44. data/docs/error-ux-review-rubric-v1.md +117 -0
  45. data/docs/error-ux-reviews/v1/records/README.md +22 -0
  46. data/docs/error-ux-round2-review-status-v1.json +27 -0
  47. data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
  48. data/docs/error-ux-round2-v1.json +1617 -0
  49. data/docs/error-ux-round2.md +111 -0
  50. data/docs/error-ux.md +54 -0
  51. data/docs/getting-started.md +80 -0
  52. data/docs/grammar-reference.md +184 -13
  53. data/docs/ielr-design.md +43 -0
  54. data/docs/ielr.md +92 -0
  55. data/docs/impact.md +101 -0
  56. data/docs/ja/bison-import.md +51 -0
  57. data/docs/ja/stable-api.md +53 -0
  58. data/docs/lexer-construction-profile.md +109 -0
  59. data/docs/lexer-migration.md +5 -0
  60. data/docs/maturity.md +129 -0
  61. data/docs/project-site-strategy.md +54 -0
  62. data/docs/racc-migration-evidence.md +45 -0
  63. data/docs/racc-migration.md +11 -0
  64. data/docs/release-readiness.md +126 -68
  65. data/docs/repair-semantics.md +123 -0
  66. data/docs/runtime-abi-evolution.md +310 -0
  67. data/docs/stability.md +145 -34
  68. data/docs/status.md +43 -0
  69. data/docs/syntax-sessions.md +183 -0
  70. data/docs/table-artifact.md +125 -0
  71. data/docs/test-interactions.md +219 -0
  72. data/docs/verification-report.md +137 -0
  73. data/docs/verifier-trust-boundary.md +199 -0
  74. data/docs/workloads.md +132 -0
  75. data/lib/ibex/analysis/digraph.rb +128 -0
  76. data/lib/ibex/analysis/sets.rb +14 -0
  77. data/lib/ibex/analysis.rb +1 -0
  78. data/lib/ibex/bison_import/importer.rb +516 -0
  79. data/lib/ibex/bison_import/tokenizer.rb +260 -0
  80. data/lib/ibex/bison_import.rb +200 -0
  81. data/lib/ibex/bounded_subprocess.rb +171 -0
  82. data/lib/ibex/cli/ambiguity.rb +14 -5
  83. data/lib/ibex/cli/analysis.rb +134 -0
  84. data/lib/ibex/cli/bison_import.rb +121 -0
  85. data/lib/ibex/cli/config.rb +128 -0
  86. data/lib/ibex/cli/diagnostics.rb +31 -11
  87. data/lib/ibex/cli/documentation.rb +11 -8
  88. data/lib/ibex/cli/equiv.rb +169 -0
  89. data/lib/ibex/cli/error_messages.rb +11 -4
  90. data/lib/ibex/cli/explain.rb +14 -5
  91. data/lib/ibex/cli/fix.rb +203 -0
  92. data/lib/ibex/cli/formatting.rb +11 -10
  93. data/lib/ibex/cli/fuzz.rb +200 -0
  94. data/lib/ibex/cli/fuzz_regressions.rb +145 -0
  95. data/lib/ibex/cli/generation_artifacts.rb +53 -2
  96. data/lib/ibex/cli/generation_error_messages.rb +1 -1
  97. data/lib/ibex/cli/grammar_tests.rb +32 -14
  98. data/lib/ibex/cli/impact.rb +393 -0
  99. data/lib/ibex/cli/ir_tools.rb +5 -61
  100. data/lib/ibex/cli/outputs.rb +60 -33
  101. data/lib/ibex/cli/reduce.rb +202 -0
  102. data/lib/ibex/cli/reduce_reporting.rb +71 -0
  103. data/lib/ibex/cli/samples.rb +49 -23
  104. data/lib/ibex/cli/verify.rb +116 -0
  105. data/lib/ibex/cli/watch.rb +6 -5
  106. data/lib/ibex/cli.rb +362 -56
  107. data/lib/ibex/codegen/action_locations.rb +1 -1
  108. data/lib/ibex/codegen/action_method_source.rb +5 -6
  109. data/lib/ibex/codegen/action_source.rb +3 -2
  110. data/lib/ibex/codegen/ambiguity.rb +5 -4
  111. data/lib/ibex/codegen/explain.rb +123 -55
  112. data/lib/ibex/codegen/generated_action_abi.rb +10 -5
  113. data/lib/ibex/codegen/rbs.rb +15 -7
  114. data/lib/ibex/codegen/report.rb +25 -13
  115. data/lib/ibex/codegen/ruby.rb +28 -64
  116. data/lib/ibex/codegen/ruby_actions.rb +9 -6
  117. data/lib/ibex/codegen/ruby_syntax.rb +15 -9
  118. data/lib/ibex/configuration/analysis_grammar.rb +35 -0
  119. data/lib/ibex/configuration/explanation.rb +421 -0
  120. data/lib/ibex/configuration/inspector.rb +232 -0
  121. data/lib/ibex/configuration.rb +580 -0
  122. data/lib/ibex/coverage/collector.rb +21 -11
  123. data/lib/ibex/coverage/event_stream.rb +13 -7
  124. data/lib/ibex/coverage/report.rb +26 -18
  125. data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
  126. data/lib/ibex/delta_reducer.rb +99 -0
  127. data/lib/ibex/diff.rb +141 -0
  128. data/lib/ibex/equiv/machine.rb +135 -0
  129. data/lib/ibex/equiv.rb +373 -0
  130. data/lib/ibex/fix.rb +557 -0
  131. data/lib/ibex/frontend/ast.rb +22 -2
  132. data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
  133. data/lib/ibex/frontend/diagnostic.rb +1 -1
  134. data/lib/ibex/frontend/formatter.rb +72 -26
  135. data/lib/ibex/frontend/generated_parser.rb +147 -117
  136. data/lib/ibex/frontend/generated_parser_base.rb +7 -3
  137. data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
  138. data/lib/ibex/frontend/generation.rb +1 -1
  139. data/lib/ibex/frontend/parser/declarations.rb +17 -2
  140. data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
  141. data/lib/ibex/frontend/regenerator.rb +1 -0
  142. data/lib/ibex/frontend/resolution.rb +2 -1
  143. data/lib/ibex/frontend/resolver.rb +1 -1
  144. data/lib/ibex/frontend/rule_documentation.rb +2 -1
  145. data/lib/ibex/frontend/source_cursor.rb +2 -2
  146. data/lib/ibex/frontend/source_span.rb +9 -1
  147. data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
  148. data/lib/ibex/frontend/token_adapter.rb +10 -0
  149. data/lib/ibex/frontend.rb +1 -0
  150. data/lib/ibex/fuzz.rb +219 -0
  151. data/lib/ibex/generation_input.rb +1 -1
  152. data/lib/ibex/generation_manifest.rb +36 -25
  153. data/lib/ibex/generation_transaction.rb +1 -1
  154. data/lib/ibex/generation_transaction_recovery.rb +4 -4
  155. data/lib/ibex/generation_transaction_validation.rb +2 -2
  156. data/lib/ibex/grammar_tests.rb +1 -1
  157. data/lib/ibex/impact/action_impact.rb +148 -0
  158. data/lib/ibex/impact/automaton_impact.rb +57 -0
  159. data/lib/ibex/impact/coverage_impact.rb +53 -0
  160. data/lib/ibex/impact/graph.rb +132 -0
  161. data/lib/ibex/impact/propagation.rb +147 -0
  162. data/lib/ibex/impact/report.rb +273 -0
  163. data/lib/ibex/impact/seeds.rb +69 -0
  164. data/lib/ibex/impact/severity.rb +139 -0
  165. data/lib/ibex/impact.rb +16 -0
  166. data/lib/ibex/ir/automaton_ir.rb +61 -19
  167. data/lib/ibex/ir/grammar_ir.rb +76 -45
  168. data/lib/ibex/ir/lexer_ir.rb +5 -5
  169. data/lib/ibex/ir/parser_contract.rb +135 -0
  170. data/lib/ibex/ir/serialize.rb +203 -76
  171. data/lib/ibex/ir/validator/automaton.rb +64 -34
  172. data/lib/ibex/ir/validator/base.rb +51 -33
  173. data/lib/ibex/ir/validator/grammar.rb +113 -83
  174. data/lib/ibex/ir/validator/lexer.rb +2 -2
  175. data/lib/ibex/ir/validator.rb +2 -1
  176. data/lib/ibex/ir.rb +16 -3
  177. data/lib/ibex/lalr/build_metrics.rb +50 -2
  178. data/lib/ibex/lalr/builder.rb +191 -56
  179. data/lib/ibex/lalr/conflict_search.rb +15 -4
  180. data/lib/ibex/lalr/counterexample.rb +19 -14
  181. data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
  182. data/lib/ibex/lalr/goto_follows.rb +229 -0
  183. data/lib/ibex/lalr/ielr/annotator.rb +214 -0
  184. data/lib/ibex/lalr/ielr/bits.rb +28 -0
  185. data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
  186. data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
  187. data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
  188. data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
  189. data/lib/ibex/lalr/ielr/split_state.rb +20 -0
  190. data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
  191. data/lib/ibex/lalr/ielr_partition.rb +22 -8
  192. data/lib/ibex/lalr/inadequacy_report.rb +50 -0
  193. data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
  194. data/lib/ibex/lalr/lr0_collection.rb +121 -0
  195. data/lib/ibex/lalr/unreachable_states.rb +80 -0
  196. data/lib/ibex/lalr.rb +52 -1
  197. data/lib/ibex/location.rb +3 -3
  198. data/lib/ibex/lsp/document_handlers.rb +9 -7
  199. data/lib/ibex/lsp/initialization_handlers.rb +9 -7
  200. data/lib/ibex/lsp/navigation_handlers.rb +25 -11
  201. data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
  202. data/lib/ibex/lsp/position_codec.rb +6 -2
  203. data/lib/ibex/lsp/request_handlers.rb +3 -2
  204. data/lib/ibex/lsp/request_support.rb +14 -7
  205. data/lib/ibex/lsp/server.rb +13 -11
  206. data/lib/ibex/lsp/symbol_index.rb +13 -13
  207. data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
  208. data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
  209. data/lib/ibex/lsp/transport.rb +13 -3
  210. data/lib/ibex/lsp/workspace.rb +5 -4
  211. data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
  212. data/lib/ibex/lsp.rb +1 -0
  213. data/lib/ibex/messages/en.yml +28 -0
  214. data/lib/ibex/messages/ja.yml +28 -0
  215. data/lib/ibex/messages.rb +68 -0
  216. data/lib/ibex/metrics.rb +175 -0
  217. data/lib/ibex/normalize/declarations.rb +2 -0
  218. data/lib/ibex/normalize/diagnostics.rb +63 -38
  219. data/lib/ibex/normalize/expander.rb +5 -4
  220. data/lib/ibex/normalize/expression.rb +8 -6
  221. data/lib/ibex/normalize/grammar_builder.rb +26 -0
  222. data/lib/ibex/normalize/inline_expansion.rb +100 -49
  223. data/lib/ibex/normalize/lexer.rb +3 -3
  224. data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
  225. data/lib/ibex/normalize/parameter_lowering.rb +40 -23
  226. data/lib/ibex/normalize/parameters.rb +31 -4
  227. data/lib/ibex/normalize/parser_configuration.rb +70 -0
  228. data/lib/ibex/normalize.rb +10 -20
  229. data/lib/ibex/racc_migration/report.rb +6 -3
  230. data/lib/ibex/rake_task.rb +1 -1
  231. data/lib/ibex/samples.rb +43 -13
  232. data/lib/ibex/table_artifact/builder.rb +266 -0
  233. data/lib/ibex/table_artifact/cst_projection.rb +70 -0
  234. data/lib/ibex/table_artifact/document.rb +39 -0
  235. data/lib/ibex/table_artifact/executor.rb +187 -0
  236. data/lib/ibex/table_artifact/serializer.rb +57 -0
  237. data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
  238. data/lib/ibex/table_artifact/validator/support.rb +86 -0
  239. data/lib/ibex/table_artifact/validator/tables.rb +238 -0
  240. data/lib/ibex/table_artifact/validator.rb +253 -0
  241. data/lib/ibex/table_artifact.rb +88 -0
  242. data/lib/ibex/table_simulation/result.rb +6 -2
  243. data/lib/ibex/table_simulation/step.rb +3 -1
  244. data/lib/ibex/tables.rb +14 -0
  245. data/lib/ibex/verifiable_generation_bundle.rb +86 -0
  246. data/lib/ibex/verification_report/builder.rb +159 -0
  247. data/lib/ibex/verification_report/canonical_ir.rb +123 -0
  248. data/lib/ibex/verification_report/logical_path.rb +68 -0
  249. data/lib/ibex/verification_report/validator.rb +329 -0
  250. data/lib/ibex/verification_report.rb +69 -0
  251. data/lib/ibex/verify/action_correspondence.rb +138 -0
  252. data/lib/ibex/verify/language_witness.rb +295 -0
  253. data/lib/ibex/verify/reference_collection.rb +190 -0
  254. data/lib/ibex/verify/result.rb +73 -0
  255. data/lib/ibex/verify/verifier.rb +598 -0
  256. data/lib/ibex/verify.rb +15 -0
  257. data/lib/ibex/version.rb +1 -1
  258. data/lib/ibex/watch/runner.rb +10 -1
  259. data/lib/ibex/watch/source_snapshot.rb +13 -8
  260. data/lib/ibex.rb +11 -0
  261. data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
  262. data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
  263. data/schema/bison-import-v1.schema.json +106 -0
  264. data/schema/conflict-explanation-review-v1.schema.json +196 -0
  265. data/schema/conflict-explanation-study-v1.schema.json +528 -0
  266. data/schema/construction-profile-v1.schema.json +779 -0
  267. data/schema/diff-v1.schema.json +41 -0
  268. data/schema/direct-ielr-decision-v1.schema.json +200 -0
  269. data/schema/equiv-v1.schema.json +64 -0
  270. data/schema/error-ux-review-v1.schema.json +609 -0
  271. data/schema/error-ux-round2-review-v1.schema.json +191 -0
  272. data/schema/error-ux-round2-v1.schema.json +519 -0
  273. data/schema/explain-v1.schema.json +63 -1
  274. data/schema/fix-v1.schema.json +64 -0
  275. data/schema/fix-v2.schema.json +66 -0
  276. data/schema/fix-v3.schema.json +101 -0
  277. data/schema/fuzz-regression-v1.schema.json +79 -0
  278. data/schema/fuzz-v1.schema.json +114 -0
  279. data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
  280. data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
  281. data/schema/grammar-ir.schema.json +271 -0
  282. data/schema/ielr-benchmark-v1.schema.json +85 -0
  283. data/schema/impact-v1.schema.json +208 -0
  284. data/schema/lexer-profile-v1.schema.json +491 -0
  285. data/schema/metrics-v1.schema.json +60 -0
  286. data/schema/reduce-v1.schema.json +22 -0
  287. data/schema/reduce-v2.schema.json +63 -0
  288. data/schema/table-artifact-v1.schema.json +377 -0
  289. data/schema/verification-report-v1.schema.json +222 -0
  290. data/schema/verify-v1.schema.json +42 -0
  291. data/sig/ibex/analysis/digraph.rbs +19 -0
  292. data/sig/ibex/analysis/sets.rbs +10 -0
  293. data/sig/ibex/bison_import/importer.rbs +140 -0
  294. data/sig/ibex/bison_import/tokenizer.rbs +91 -0
  295. data/sig/ibex/bison_import.rbs +95 -0
  296. data/sig/ibex/bounded_subprocess.rbs +76 -0
  297. data/sig/ibex/cli/ambiguity.rbs +6 -0
  298. data/sig/ibex/cli/analysis.rbs +43 -0
  299. data/sig/ibex/cli/bison_import.rbs +32 -0
  300. data/sig/ibex/cli/config.rbs +35 -0
  301. data/sig/ibex/cli/diagnostics.rbs +16 -3
  302. data/sig/ibex/cli/documentation.rbs +5 -1
  303. data/sig/ibex/cli/equiv.rbs +49 -0
  304. data/sig/ibex/cli/error_messages.rbs +6 -0
  305. data/sig/ibex/cli/explain.rbs +6 -0
  306. data/sig/ibex/cli/fix.rbs +53 -0
  307. data/sig/ibex/cli/formatting.rbs +5 -1
  308. data/sig/ibex/cli/fuzz.rbs +47 -0
  309. data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
  310. data/sig/ibex/cli/generation_artifacts.rbs +21 -2
  311. data/sig/ibex/cli/generation_error_messages.rbs +2 -2
  312. data/sig/ibex/cli/grammar_tests.rbs +15 -3
  313. data/sig/ibex/cli/impact.rbs +95 -0
  314. data/sig/ibex/cli/ir_tools.rbs +7 -19
  315. data/sig/ibex/cli/outputs.rbs +9 -3
  316. data/sig/ibex/cli/reduce.rbs +60 -0
  317. data/sig/ibex/cli/reduce_reporting.rbs +27 -0
  318. data/sig/ibex/cli/samples.rbs +8 -0
  319. data/sig/ibex/cli/verify.rbs +28 -0
  320. data/sig/ibex/cli/watch.rbs +2 -2
  321. data/sig/ibex/cli.rbs +79 -5
  322. data/sig/ibex/codegen/action_method_source.rbs +4 -4
  323. data/sig/ibex/codegen/explain.rbs +45 -25
  324. data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
  325. data/sig/ibex/codegen/rbs.rbs +12 -10
  326. data/sig/ibex/codegen/report.rbs +12 -12
  327. data/sig/ibex/codegen/ruby.rbs +18 -15
  328. data/sig/ibex/codegen/ruby_actions.rbs +6 -6
  329. data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
  330. data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
  331. data/sig/ibex/configuration/explanation.rbs +195 -0
  332. data/sig/ibex/configuration/inspector.rbs +53 -0
  333. data/sig/ibex/configuration.rbs +242 -0
  334. data/sig/ibex/coverage/collector.rbs +16 -14
  335. data/sig/ibex/coverage/event_stream.rbs +12 -10
  336. data/sig/ibex/coverage/report.rbs +20 -18
  337. data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
  338. data/sig/ibex/delta_reducer.rbs +38 -0
  339. data/sig/ibex/diff.rbs +47 -0
  340. data/sig/ibex/equiv/machine.rbs +54 -0
  341. data/sig/ibex/equiv.rbs +129 -0
  342. data/sig/ibex/fix.rbs +138 -0
  343. data/sig/ibex/frontend/ast.rbs +33 -7
  344. data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
  345. data/sig/ibex/frontend/diagnostic.rbs +2 -2
  346. data/sig/ibex/frontend/formatter.rbs +38 -30
  347. data/sig/ibex/frontend/generated_parser.rbs +87 -77
  348. data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
  349. data/sig/ibex/frontend/parser/declarations.rbs +3 -0
  350. data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
  351. data/sig/ibex/frontend/resolution.rbs +3 -2
  352. data/sig/ibex/frontend/source_cursor.rbs +2 -2
  353. data/sig/ibex/frontend/source_span.rbs +5 -2
  354. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
  355. data/sig/ibex/frontend/token_adapter.rbs +6 -0
  356. data/sig/ibex/fuzz.rbs +71 -0
  357. data/sig/ibex/generation_input.rbs +2 -2
  358. data/sig/ibex/generation_manifest.rbs +35 -32
  359. data/sig/ibex/generation_transaction_recovery.rbs +2 -2
  360. data/sig/ibex/generation_transaction_validation.rbs +2 -2
  361. data/sig/ibex/grammar_tests.rbs +2 -2
  362. data/sig/ibex/impact/action_impact.rbs +58 -0
  363. data/sig/ibex/impact/automaton_impact.rbs +37 -0
  364. data/sig/ibex/impact/coverage_impact.rbs +28 -0
  365. data/sig/ibex/impact/graph.rbs +67 -0
  366. data/sig/ibex/impact/propagation.rbs +56 -0
  367. data/sig/ibex/impact/report.rbs +107 -0
  368. data/sig/ibex/impact/seeds.rbs +36 -0
  369. data/sig/ibex/impact/severity.rbs +55 -0
  370. data/sig/ibex/impact.rbs +6 -0
  371. data/sig/ibex/ir/automaton_ir.rbs +30 -11
  372. data/sig/ibex/ir/grammar_ir.rbs +41 -27
  373. data/sig/ibex/ir/lexer_ir.rbs +8 -8
  374. data/sig/ibex/ir/parser_contract.rbs +69 -0
  375. data/sig/ibex/ir/serialize.rbs +63 -28
  376. data/sig/ibex/ir/validator/automaton.rbs +47 -44
  377. data/sig/ibex/ir/validator/base.rbs +35 -31
  378. data/sig/ibex/ir/validator/grammar.rbs +78 -67
  379. data/sig/ibex/ir/validator/lexer.rbs +4 -4
  380. data/sig/ibex/ir.rbs +9 -3
  381. data/sig/ibex/lalr/build_metrics.rbs +42 -2
  382. data/sig/ibex/lalr/builder.rbs +75 -24
  383. data/sig/ibex/lalr/conflict_search.rbs +5 -2
  384. data/sig/ibex/lalr/counterexample.rbs +17 -14
  385. data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
  386. data/sig/ibex/lalr/goto_follows.rbs +89 -0
  387. data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
  388. data/sig/ibex/lalr/ielr/bits.rbs +14 -0
  389. data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
  390. data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
  391. data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
  392. data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
  393. data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
  394. data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
  395. data/sig/ibex/lalr/ielr_partition.rbs +17 -5
  396. data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
  397. data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
  398. data/sig/ibex/lalr/lr0_collection.rbs +50 -0
  399. data/sig/ibex/lalr/unreachable_states.rbs +25 -0
  400. data/sig/ibex/lalr.rbs +11 -1
  401. data/sig/ibex/location.rbs +6 -6
  402. data/sig/ibex/lsp/document_handlers.rbs +8 -8
  403. data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
  404. data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
  405. data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
  406. data/sig/ibex/lsp/position_codec.rbs +8 -4
  407. data/sig/ibex/lsp/request_handlers.rbs +4 -4
  408. data/sig/ibex/lsp/request_support.rbs +8 -8
  409. data/sig/ibex/lsp/server.rbs +16 -16
  410. data/sig/ibex/lsp/symbol_index.rbs +20 -21
  411. data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
  412. data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
  413. data/sig/ibex/lsp/transport.rbs +18 -6
  414. data/sig/ibex/lsp/workspace.rbs +3 -2
  415. data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
  416. data/sig/ibex/messages.rbs +26 -0
  417. data/sig/ibex/metrics.rbs +50 -0
  418. data/sig/ibex/normalize/diagnostics.rbs +4 -4
  419. data/sig/ibex/normalize/expander.rbs +2 -2
  420. data/sig/ibex/normalize/expression.rbs +10 -8
  421. data/sig/ibex/normalize/grammar_builder.rbs +11 -0
  422. data/sig/ibex/normalize/inline_expansion.rbs +59 -50
  423. data/sig/ibex/normalize/lexer.rbs +4 -4
  424. data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
  425. data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
  426. data/sig/ibex/normalize/parameters.rbs +13 -8
  427. data/sig/ibex/normalize/parser_configuration.rbs +23 -0
  428. data/sig/ibex/normalize.rbs +11 -5
  429. data/sig/ibex/racc_migration/report.rbs +8 -6
  430. data/sig/ibex/rake_task.rbs +1 -1
  431. data/sig/ibex/samples.rbs +13 -4
  432. data/sig/ibex/table_artifact/builder.rbs +93 -0
  433. data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
  434. data/sig/ibex/table_artifact/document.rbs +27 -0
  435. data/sig/ibex/table_artifact/executor.rbs +66 -0
  436. data/sig/ibex/table_artifact/serializer.rbs +25 -0
  437. data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
  438. data/sig/ibex/table_artifact/validator/support.rbs +46 -0
  439. data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
  440. data/sig/ibex/table_artifact/validator.rbs +66 -0
  441. data/sig/ibex/table_artifact.rbs +32 -0
  442. data/sig/ibex/table_simulation/result.rbs +8 -4
  443. data/sig/ibex/table_simulation/step.rbs +4 -2
  444. data/sig/ibex/tables.rbs +10 -9
  445. data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
  446. data/sig/ibex/verification_report/builder.rbs +53 -0
  447. data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
  448. data/sig/ibex/verification_report/logical_path.rbs +34 -0
  449. data/sig/ibex/verification_report/validator.rbs +114 -0
  450. data/sig/ibex/verification_report.rbs +44 -0
  451. data/sig/ibex/verify/action_correspondence.rbs +50 -0
  452. data/sig/ibex/verify/language_witness.rbs +112 -0
  453. data/sig/ibex/verify/reference_collection.rbs +63 -0
  454. data/sig/ibex/verify/result.rbs +52 -0
  455. data/sig/ibex/verify/verifier.rbs +170 -0
  456. data/sig/ibex/verify.rbs +6 -0
  457. data/sig/ibex/watch/runner.rbs +12 -2
  458. data/sig/ibex/watch/source_snapshot.rbs +13 -9
  459. metadata +254 -12
  460. data/lib/ibex/ir/migration.rb +0 -120
  461. data/sig/ibex/ir/migration.rbs +0 -34
@@ -3,6 +3,7 @@
3
3
  require_relative "generated_parser_metadata"
4
4
  require_relative "generated_parser_includes"
5
5
  require_relative "generated_parser_parameters"
6
+ require_relative "parser_configuration_support"
6
7
 
7
8
  module Ibex
8
9
  module Frontend
@@ -12,6 +13,7 @@ module Ibex
12
13
  include GeneratedParserMetadata
13
14
  include GeneratedParserIncludes
14
15
  include GeneratedParserParameters
16
+ include ParserConfigurationSupport
15
17
 
16
18
  attr_reader :diagnostic_token #: Token?
17
19
 
@@ -29,7 +31,7 @@ module Ibex
29
31
 
30
32
  # @rbs () -> (AST::Root | AST::Fragment)
31
33
  def parse
32
- do_parse
34
+ do_parse #: AST::Root | AST::Fragment
33
35
  end
34
36
 
35
37
  # @rbs () -> ([external_token, Token] | false)
@@ -42,7 +44,7 @@ module Ibex
42
44
  raise
43
45
  end
44
46
 
45
- # @rbs (Integer? _token_id, Token? value, Array[untyped] _value_stack) -> bot
47
+ # @rbs (Integer? _token_id, Token? value, Array[Object?] _value_stack) -> bot
46
48
  def on_error(_token_id, value, _value_stack)
47
49
  token = value || @adapter.eof_token
48
50
  @diagnostic_token = token
@@ -104,10 +106,12 @@ module Ibex
104
106
  # @rbs (Token class_token, Array[String] class_parts, Array[String]? superclass,
105
107
  # Array[AST::declaration] declarations, Array[AST::Rule] rules, AST::user_code user_code) -> AST::Root
106
108
  def build_root(class_token, class_parts, superclass, declarations, rules, user_code)
109
+ validate_root_parser_configuration(declarations)
107
110
  AST::Root.new(class_name: class_parts.join("::"), superclass: superclass&.join("::"),
108
111
  declarations: declarations, rules: rules, user_code: user_code, loc: class_token.location,
109
112
  extended: @mode == :extended || @adapter.extended_pragma? || @adapter.cst_pragma?,
110
- cst: @adapter.cst_pragma?)
113
+ cst: @adapter.cst_pragma?,
114
+ extended_loc: @adapter.extended_pragma_location || @adapter.cst_pragma_location)
111
115
  end
112
116
 
113
117
  # @rbs (Token keyword, Array[[String, String?]] entries) -> AST::Tokens
@@ -44,17 +44,7 @@ module Ibex
44
44
 
45
45
  # @rbs (AST::declaration declaration) -> String?
46
46
  def fragment_root_declaration_name(declaration)
47
- return "options" if declaration.is_a?(AST::Options)
48
- return "expect" if declaration.is_a?(AST::Expect)
49
- return "%expect-rr" if declaration.is_a?(AST::ExpectRR)
50
- return "%param" if declaration.is_a?(AST::Parameter)
51
- return "start" if declaration.is_a?(AST::Start)
52
- return "%recover" if declaration.is_a?(AST::Recovery)
53
- return "%on_error_reduce" if declaration.is_a?(AST::OnErrorReduce)
54
- return "%test" if declaration.is_a?(AST::GrammarTest)
55
- return "lexer" if declaration.is_a?(AST::Lexer)
56
-
57
- nil
47
+ AST::ROOT_ONLY_DECLARATION_NAMES.find { |node_class, _name| declaration.is_a?(node_class) }&.last
58
48
  end
59
49
  end
60
50
  end
@@ -5,7 +5,7 @@
5
5
  # the DSL, and every other frontend facility.
6
6
  require_relative "../error"
7
7
  require_relative "../tables"
8
- require "ibex/runtime"
8
+ require "ibex/runtime/parser"
9
9
  require_relative "source_span"
10
10
  require_relative "source_cursor"
11
11
  require_relative "source_document"
@@ -8,7 +8,7 @@ module Ibex
8
8
  module BootstrapParserDeclarations
9
9
  DECLARATIONS = %w[
10
10
  include import token prechigh preclow options expect expect_rr start recover on_error_reduce
11
- test lexer convert display type param printer pragma rule
11
+ test lexer convert display type param printer parser pragma rule
12
12
  ].freeze #: Array[String]
13
13
  ASSOCIATIVITIES = %w[left right nonassoc precedence].freeze #: Array[String]
14
14
 
@@ -33,7 +33,7 @@ module Ibex
33
33
  fail_at(value.location, "unknown pragma #{name}") unless %w[extended cst].include?(name)
34
34
  fail_at(keyword.location, "duplicate pragma #{name}") if @pragmas[name]
35
35
 
36
- @pragmas[name] = true
36
+ @pragmas[name] = keyword.location
37
37
  @mode = :extended
38
38
  end
39
39
  end
@@ -58,11 +58,26 @@ module Ibex
58
58
  when "type" then parse_symbol_metadata(AST::SemanticType, "type")
59
59
  when "param" then parse_parameter
60
60
  when "printer" then parse_printer
61
+ when "parser" then parse_parser_configuration
61
62
  when "pragma" then fail_at(current.location, "expected rule, got pragma")
62
63
  else fail_expected("a declaration or rule")
63
64
  end
64
65
  end
65
66
 
67
+ # @rbs () -> AST::ParserConfiguration
68
+ def parse_parser_configuration
69
+ # @type self: BootstrapParser
70
+ keyword = advance
71
+ settings = [] #: Array[AST::ParserSetting]
72
+ until keyword?("end") || current.type == :eof
73
+ key = expect(:identifier)
74
+ value = expect(:identifier)
75
+ settings << build_parser_setting(key, value)
76
+ end
77
+ expect_keyword("end")
78
+ build_parser_configuration(keyword, settings)
79
+ end
80
+
66
81
  # @rbs () -> AST::Include
67
82
  def parse_import
68
83
  # @type self: BootstrapParser
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Shares the closed parser-declaration semantics between bootstrap and generated frontends.
6
+ module ParserConfigurationSupport
7
+ # @rbs!
8
+ # private def token_string: (Token) -> String
9
+ # private def fail_at: (Location, String) -> bot
10
+ # private def extended_only!: (Location, String) -> void
11
+
12
+ PARSER_SETTING_VALUES = {
13
+ "algorithm" => %w[slr lalr ielr lr1].freeze,
14
+ "entries" => %w[shared isolated].freeze,
15
+ "cst_trivia" => %w[leading balanced drop].freeze
16
+ }.freeze #: Hash[String, Array[String]]
17
+
18
+ private
19
+
20
+ # @rbs (Token key, Token value) -> AST::ParserSetting
21
+ def build_parser_setting(key, value)
22
+ name = token_string(key)
23
+ admitted = PARSER_SETTING_VALUES[name]
24
+ fail_at(key.location, "unknown parser setting #{name}") unless admitted
25
+
26
+ selected = token_string(value)
27
+ unless admitted.include?(selected)
28
+ fail_at(value.location, "parser.#{name} must be one of #{admitted.join(', ')}; got #{selected}")
29
+ end
30
+
31
+ AST::ParserSetting.new(key: name.to_sym, value: selected.to_sym, loc: key.location)
32
+ end
33
+
34
+ # @rbs (Token keyword, Array[AST::ParserSetting] settings) -> AST::ParserConfiguration
35
+ def build_parser_configuration(keyword, settings)
36
+ extended_only!(keyword.location, "parser declarations")
37
+ seen = {} #: Hash[Symbol, bool]
38
+ settings.each do |setting|
39
+ fail_at(setting.loc, "duplicate parser setting #{setting.key}") if seen[setting.key]
40
+
41
+ seen[setting.key] = true
42
+ end
43
+ fail_at(keyword.location, "parser declaration requires at least one setting") if settings.empty?
44
+
45
+ AST::ParserConfiguration.new(settings: settings, loc: keyword.location)
46
+ end
47
+
48
+ # @rbs (Array[AST::declaration] declarations) -> void
49
+ def validate_root_parser_configuration(declarations)
50
+ configurations = declarations.grep(AST::ParserConfiguration)
51
+ duplicate = configurations.fetch(1, nil)
52
+ fail_at(duplicate.loc, "duplicate parser declaration") if duplicate
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,6 +8,7 @@ require_relative "source_document"
8
8
  require_relative "action_scanner"
9
9
  require_relative "lexer"
10
10
  require_relative "ast"
11
+ require_relative "parser_configuration_support"
11
12
  require_relative "parser/declarations"
12
13
  require_relative "parser/rules"
13
14
  require_relative "parser/parameters"
@@ -36,7 +36,8 @@ module Ibex
36
36
 
37
37
  private
38
38
 
39
- # @rbs (untyped value) -> untyped
39
+ # AST payloads are recursively heterogeneous and are only traversed to freeze them.
40
+ # @rbs (Object? value) -> Object?
40
41
  def deep_freeze_ast(value)
41
42
  case value
42
43
  when Struct
@@ -44,7 +44,7 @@ module Ibex
44
44
  root = AST::Root.new(
45
45
  class_name: parsed_root.class_name, superclass: parsed_root.superclass,
46
46
  declarations: declarations, rules: rules, user_code: parsed_root.user_code, loc: parsed_root.loc,
47
- extended: parsed_root.extended, cst: parsed_root.cst
47
+ extended: parsed_root.extended, cst: parsed_root.cst, extended_loc: parsed_root.extended_loc
48
48
  )
49
49
  @loaded[@root_path] = true
50
50
  @visiting.pop
@@ -26,7 +26,8 @@ module Ibex
26
26
  if node.is_a?(AST::Root)
27
27
  AST::Root.new(
28
28
  class_name: node.class_name, superclass: node.superclass, declarations: node.declarations,
29
- rules: rules, user_code: node.user_code, loc: node.loc, extended: node.extended, cst: node.cst
29
+ rules: rules, user_code: node.user_code, loc: node.loc, extended: node.extended, cst: node.cst,
30
+ extended_loc: node.extended_loc
30
31
  )
31
32
  else
32
33
  AST::Fragment.new(declarations: node.declarations, rules: rules, loc: node.loc)
@@ -32,7 +32,7 @@ module Ibex
32
32
  )
33
33
 
34
34
  class Token
35
- # @rbs () -> Hash[Symbol, untyped]
35
+ # @rbs () -> Hash[Symbol, Object?]
36
36
  def to_h
37
37
  { type: type, value: value, location: location.to_h }
38
38
  end
@@ -50,7 +50,7 @@ module Ibex
50
50
  # @rbs (String source, String file) -> void
51
51
  def initialize(source, file)
52
52
  @source = SourceEncoding.validated_utf8(source, file)
53
- @file = file.dup.freeze
53
+ @file = SourceEncoding.validated_file(file)
54
54
  @ascii_only = @source.ascii_only?
55
55
  @character_length = @ascii_only ? @source.bytesize : @source.length
56
56
  @index = 0
@@ -11,6 +11,14 @@ module Ibex
11
11
 
12
12
  validated.freeze
13
13
  end
14
+
15
+ # @rbs (String file) -> String
16
+ def self.validated_file(file)
17
+ validated = file.dup.force_encoding(Encoding::UTF_8)
18
+ raise Ibex::Error, "#{file}: file path must be valid UTF-8" unless validated.valid_encoding?
19
+
20
+ validated.freeze
21
+ end
14
22
  end
15
23
 
16
24
  # An immutable position in grammar source.
@@ -72,7 +80,7 @@ module Ibex
72
80
  length.zero?
73
81
  end
74
82
 
75
- # @rbs () -> Hash[Symbol, untyped]
83
+ # @rbs () -> Hash[Symbol, Object?]
76
84
  def to_h
77
85
  { file: file, start: start.to_h, end: finish.to_h }
78
86
  end
@@ -26,6 +26,7 @@ module Ibex
26
26
  "display" => %i[DISPLAY display_symbol], "type" => %i[TYPE type_symbol],
27
27
  "param" => %i[PARAM param_name],
28
28
  "printer" => %i[PRINTER printer_symbol],
29
+ "parser" => %i[PARSER parser_key],
29
30
  "rule" => %i[RULE rules]
30
31
  }.freeze #: Hash[String, [external_token, Symbol]]
31
32
  ASSOCIATIONS = {
@@ -49,7 +50,8 @@ module Ibex
49
50
  test_source: "a double-quoted string",
50
51
  lexer_entries: "a lexer rule, state, or end", lexer_state_name: "a state name",
51
52
  lexer_state_do: "do", lexer_pattern: "a regular expression or quoted literal",
52
- lexer_action_or_entry: "an action, lexer rule, state, or end"
53
+ lexer_action_or_entry: "an action, lexer rule, state, or end",
54
+ parser_key: "a parser setting or end", parser_value: "a parser setting value"
53
55
  }.freeze #: Hash[Symbol, String]
54
56
 
55
57
  attr_reader :conversion_name #: Token?
@@ -59,13 +61,13 @@ module Ibex
59
61
 
60
62
  # @rbs @extended_mode: bool
61
63
  # @rbs @fragment: bool?
62
- # @rbs @pragmas: Hash[String, bool]
64
+ # @rbs @pragmas: Hash[String, Location]
63
65
  # @rbs @pragma_location: Location?
64
66
 
65
67
  # @rbs (?extended: bool) -> void
66
68
  def initialize(extended: false)
67
69
  @extended_mode = extended
68
- @pragmas = {} #: Hash[String, bool]
70
+ @pragmas = {} #: Hash[String, Location]
69
71
  @state = :class_keyword
70
72
  end
71
73
 
@@ -84,12 +86,22 @@ module Ibex
84
86
 
85
87
  # @rbs () -> bool
86
88
  def extended_pragma?
87
- @pragmas["extended"] == true
89
+ @pragmas.key?("extended")
88
90
  end
89
91
 
90
92
  # @rbs () -> bool
91
93
  def cst_pragma?
92
- @pragmas["cst"] == true
94
+ @pragmas.key?("cst")
95
+ end
96
+
97
+ # @rbs () -> Location?
98
+ def extended_pragma_location
99
+ @pragmas["extended"]
100
+ end
101
+
102
+ # @rbs () -> Location?
103
+ def cst_pragma_location
104
+ @pragmas["cst"]
93
105
  end
94
106
 
95
107
  # @rbs (Token? token) -> String?
@@ -134,10 +146,30 @@ module Ibex
134
146
  when :test_expectation then begin_test_source(token)
135
147
  when :pragma_value then finish_pragma(token)
136
148
  when :convert_name then begin_conversion(token, :IDENTIFIER, remaining)
149
+ when :parser_key then begin_parser_setting(token)
150
+ when :parser_value then finish_parser_setting
137
151
  else :IDENTIFIER
138
152
  end
139
153
  end
140
154
 
155
+ # @rbs (Token token) -> external_token
156
+ def begin_parser_setting(token)
157
+ if string_value(token) == "end"
158
+ @state = :declaration
159
+ @declaration = nil
160
+ return :END
161
+ end
162
+
163
+ @state = :parser_value
164
+ :IDENTIFIER
165
+ end
166
+
167
+ # @rbs () -> external_token
168
+ def finish_parser_setting
169
+ @state = :parser_key
170
+ :IDENTIFIER
171
+ end
172
+
141
173
  # @rbs (Array[Token] remaining) -> external_token
142
174
  def constant_name(remaining)
143
175
  following = remaining.first
@@ -179,7 +211,7 @@ module Ibex
179
211
  location = @pragma_location || token.location
180
212
  raise Ibex::Error, "#{location}: duplicate pragma #{value}" if @pragmas[value]
181
213
 
182
- @pragmas[value] = true
214
+ @pragmas[value] = location
183
215
  @pragma_location = nil
184
216
  @state = :declaration
185
217
  @declaration = nil
@@ -65,6 +65,16 @@ module Ibex
65
65
  @declarations.cst_pragma?
66
66
  end
67
67
 
68
+ # @rbs () -> Location?
69
+ def extended_pragma_location
70
+ @declarations.extended_pragma_location
71
+ end
72
+
73
+ # @rbs () -> Location?
74
+ def cst_pragma_location
75
+ @declarations.cst_pragma_location
76
+ end
77
+
68
78
  # @rbs () -> parser_section
69
79
  def section
70
80
  rules = @rules
data/lib/ibex/frontend.rb CHANGED
@@ -11,6 +11,7 @@ require_relative "frontend/action_scanner"
11
11
  require_relative "frontend/lexer"
12
12
  require_relative "frontend/lexer_recovery"
13
13
  require_relative "frontend/ast"
14
+ require_relative "frontend/parser_configuration_support"
14
15
  require_relative "frontend/rule_documentation"
15
16
  require_relative "frontend/token_adapter/declaration_document_state"
16
17
  require_relative "frontend/token_adapter/declaration_state"
data/lib/ibex/fuzz.rb ADDED
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "samples"
5
+ require_relative "table_simulation"
6
+ require_relative "delta_reducer"
7
+
8
+ module Ibex
9
+ # @rbs!
10
+ # type fuzz_outcome = Symbol | [Symbol, String, String]
11
+ # type fuzz_mismatch = {
12
+ # tokens: Array[String],
13
+ # kind: Symbol,
14
+ # sentence: Integer,
15
+ # outcomes: Hash[Symbol, fuzz_outcome],
16
+ # bounds: Hash[Symbol, Integer]
17
+ # }
18
+ # type fuzz_budget = {
19
+ # message: String,
20
+ # bounds: Hash[Symbol, Integer],
21
+ # ?tokens: Array[String],
22
+ # ?phase: String
23
+ # }
24
+
25
+ # Bounded grammar-derived differential fuzzing without semantic execution.
26
+ class Fuzz
27
+ ALGORITHMS = %i[slr lalr ielr lr1].freeze #: Array[Symbol]
28
+ DEFAULT_MAX_ACTIONS = 100_000 #: Integer
29
+ DEFAULT_MAX_STACK = 10_000 #: Integer
30
+
31
+ class Mismatch < Ibex::Error
32
+ attr_reader :details #: fuzz_mismatch
33
+
34
+ # @rbs (fuzz_mismatch details) -> void
35
+ def initialize(details)
36
+ @details = IR.deep_freeze(details)
37
+ super("(fuzz):1:1: differential mismatch for #{details[:tokens].inspect}")
38
+ end
39
+ end
40
+
41
+ class BudgetExceeded < Ibex::Error
42
+ attr_reader :details #: fuzz_budget
43
+
44
+ # @rbs (fuzz_budget details) -> void
45
+ def initialize(details)
46
+ @details = IR.deep_freeze(details)
47
+ super("(fuzz):1:1: configured budget was exhausted: #{details[:message]}")
48
+ end
49
+ end
50
+
51
+ # @rbs (IR::Grammar grammar, ?seed: Integer, ?count: Integer, ?max_tokens: Integer,
52
+ # ?max_depth: Integer, ?max_expansions: Integer, ?max_actions: Integer, ?max_stack: Integer,
53
+ # ?coverage_guided: bool, ?path_length: Integer, ?algorithms: Array[Symbol],
54
+ # ?ielr_strategy: Symbol, ?automata: Hash[Symbol, IR::Automaton]?, ?against: (^(Array[String]) -> Symbol)?,
55
+ # ?against_description: Hash[Symbol, Object?]?) -> void
56
+ # rubocop:disable Metrics/ParameterLists
57
+ def initialize(grammar, seed: 0, count: 100, max_tokens: 32, max_depth: 16,
58
+ max_expansions: Samples::DEFAULT_MAX_EXPANSIONS, max_actions: DEFAULT_MAX_ACTIONS,
59
+ max_stack: DEFAULT_MAX_STACK, coverage_guided: false, path_length: 2,
60
+ algorithms: ALGORITHMS, ielr_strategy: :partition, automata: nil,
61
+ against: nil, against_description: nil)
62
+ raise ArgumentError, "count must be positive" unless count.positive?
63
+ raise ArgumentError, "algorithms must not be empty" if algorithms.empty?
64
+ unless LALR::Builder::IELR_STRATEGIES.include?(ielr_strategy.to_sym)
65
+ raise ArgumentError, "unknown IELR construction strategy #{ielr_strategy.inspect}"
66
+ end
67
+
68
+ @grammar = grammar
69
+ @seed = seed
70
+ @count = count
71
+ @random = Random.new(seed ^ 0x1BE)
72
+ @max_tokens = max_tokens
73
+ @max_depth = max_depth
74
+ @max_expansions = max_expansions
75
+ @max_actions = max_actions
76
+ @max_stack = max_stack
77
+ @coverage_guided = coverage_guided
78
+ @path_length = path_length
79
+ @algorithms = algorithms.map(&:to_sym).freeze
80
+ @ielr_strategy = ielr_strategy.to_sym
81
+ @automata = automata || build_automata
82
+ @against = against
83
+ @against_description = against_description
84
+ end
85
+ # rubocop:enable Metrics/ParameterLists
86
+
87
+ # @rbs () -> Hash[Symbol, Object?]
88
+ def run
89
+ sentences = generate_sentences
90
+ mutation_count = 0
91
+ sentences.each_with_index do |tokens, index|
92
+ compare!(tokens, kind: :generated, sentence: index)
93
+ mutations(tokens).each do |mutation|
94
+ compare!(mutation, kind: :mutation, sentence: index)
95
+ mutation_count += 1
96
+ end
97
+ end
98
+ successful_report(sentences.length, mutation_count)
99
+ rescue Ibex::Error => e
100
+ raise if e.is_a?(Mismatch) || e.is_a?(BudgetExceeded) || !budget_error?(e)
101
+
102
+ raise BudgetExceeded.new(
103
+ message: e.message,
104
+ bounds: { max_expansions: @max_expansions, max_actions: @max_actions, max_stack: @max_stack }
105
+ )
106
+ end
107
+
108
+ # Minimize one observed mismatch without changing its kind or outcomes.
109
+ # @rbs (Mismatch mismatch, ?max_trials: Integer) -> DeltaReducer::Result
110
+ def minimize(mismatch, max_trials: 1_000)
111
+ details = mismatch.details
112
+ original = details[:tokens]
113
+ kind = details[:kind].to_sym
114
+ sentence = details[:sentence]
115
+ outcomes = details[:outcomes]
116
+ DeltaReducer.new(max_trials: max_trials).minimize(original) do |candidate|
117
+ mismatch_reproduced?(candidate, kind: kind, sentence: sentence, outcomes: outcomes)
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ # @rbs () -> Array[Array[String]]
124
+ def generate_sentences
125
+ Samples.new(
126
+ @grammar, seed: @seed, max_tokens: @max_tokens, max_depth: @max_depth,
127
+ max_expansions: @max_expansions, strategy: @coverage_guided ? :coverage : :random,
128
+ path_length: @path_length
129
+ ).generate(count: @count)
130
+ end
131
+
132
+ # @rbs (Integer sentence_count, Integer mutation_count) -> untyped
133
+ def successful_report(sentence_count, mutation_count)
134
+ report = {
135
+ ibex_report: "fuzz", schema_version: 1, seed: @seed,
136
+ bounds: {
137
+ sentences: @count, max_tokens: @max_tokens, max_depth: @max_depth,
138
+ max_expansions: @max_expansions, max_actions: @max_actions, max_stack: @max_stack
139
+ },
140
+ strategy: @coverage_guided ? "coverage" : "random",
141
+ path_length: @path_length,
142
+ ielr_strategy: @ielr_strategy.to_s,
143
+ algorithms: @algorithms.map(&:to_s),
144
+ generated_sentences: sentence_count,
145
+ mutated_sentences: mutation_count,
146
+ result: "no_difference_within_bounds"
147
+ }
148
+ report[:external] = @against_description if @against_description
149
+ IR.deep_freeze(report)
150
+ end
151
+
152
+ # @rbs () -> Hash[Symbol, IR::Automaton]
153
+ def build_automata
154
+ @algorithms.to_h do |algorithm|
155
+ builder = LALR::Builder.new(
156
+ @grammar,
157
+ algorithm: algorithm,
158
+ ielr_strategy: @ielr_strategy
159
+ )
160
+ [algorithm, builder.build]
161
+ end
162
+ end
163
+
164
+ # @rbs (Array[String] tokens, kind: Symbol, sentence: Integer) -> void
165
+ def compare!(tokens, kind:, sentence:)
166
+ outcomes = @algorithms.to_h do |algorithm|
167
+ automaton = @automata.fetch(algorithm)
168
+ simulator = TableSimulation::Simulator.new(
169
+ automaton, max_steps: @max_actions, max_stack: @max_stack
170
+ )
171
+ [algorithm, simulator.simulate(tokens).status]
172
+ rescue Ibex::Error => e
173
+ if budget_error?(e)
174
+ raise BudgetExceeded.new(
175
+ message: e.message, tokens: tokens,
176
+ bounds: { max_actions: @max_actions, max_stack: @max_stack }
177
+ )
178
+ end
179
+ [algorithm, [:failure, e.class.name, e.message]]
180
+ end
181
+ outcomes[:external] = @against.call(tokens) if @against
182
+ values = outcomes.values.uniq
183
+ return if values.length == 1 && (kind != :generated || values.first == :accepted)
184
+
185
+ raise Mismatch.new(tokens: tokens, kind: kind, sentence: sentence, outcomes: outcomes,
186
+ bounds: { max_actions: @max_actions, max_stack: @max_stack })
187
+ end
188
+
189
+ # @rbs (Array[String] tokens) -> Array[Array[String]]
190
+ def mutations(tokens)
191
+ terminals = @grammar.terminals.reject(&:reserved).map(&:name)
192
+ replacement = terminals.fetch(@random.rand(terminals.length))
193
+ index = tokens.empty? ? 0 : @random.rand(tokens.length)
194
+ inserted = tokens.dup.insert(index, replacement)
195
+ return [inserted] if tokens.empty?
196
+
197
+ deleted = tokens.dup.tap { |items| items.delete_at(index) }
198
+ replaced = tokens.dup.tap { |items| items[index] = replacement }
199
+ [inserted, deleted, replaced].uniq
200
+ end
201
+
202
+ # @rbs (Array[String] tokens, kind: Symbol, sentence: Integer,
203
+ # outcomes: Hash[Symbol, fuzz_outcome]) -> bool
204
+ def mismatch_reproduced?(tokens, kind:, sentence:, outcomes:)
205
+ compare!(tokens, kind: kind, sentence: sentence)
206
+ false
207
+ rescue Mismatch => e
208
+ e.details[:kind].to_sym == kind &&
209
+ e.details[:outcomes] == outcomes
210
+ end
211
+
212
+ # @rbs (Ibex::Error error) -> bool
213
+ def budget_error?(error)
214
+ error.message.match?(
215
+ /simulation exceeded|(?:limit|maximum|budget).*(?:exceed|exhaust)|exceed.*(?:limit|maximum|budget)/i
216
+ )
217
+ end
218
+ end
219
+ end
@@ -31,7 +31,7 @@ module Ibex
31
31
  @access_paths << expanded unless @access_paths.include?(expanded)
32
32
  end
33
33
 
34
- # @rbs () -> Hash[String, untyped]
34
+ # @rbs () -> Hash[String, String | Integer]
35
35
  def to_h
36
36
  { "path" => @path, "sha256" => @sha256, "bytesize" => @bytesize }
37
37
  end