ibex 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +95 -272
- data/docs/architecture.md +187 -35
- data/docs/bison-import.md +103 -0
- data/docs/comparison-policy.md +127 -0
- data/docs/configuration-model.md +59 -0
- data/docs/configuration-report.md +28 -0
- data/docs/conflict-explanation-reviews/v1/records/README.md +28 -0
- data/docs/conflict-explanation-study.md +62 -0
- data/docs/construction-profiling.md +157 -0
- data/docs/cst-migration.md +3 -4
- data/docs/cst.md +24 -0
- data/docs/decisions/0000-template.md +24 -0
- data/docs/decisions/0001-separate-ir-pipeline.md +34 -0
- data/docs/decisions/0002-opaque-user-code-boundary.md +32 -0
- data/docs/decisions/0003-self-hosted-grammar-frontend.md +32 -0
- data/docs/decisions/0004-shared-semantic-and-lossless-source-model.md +33 -0
- data/docs/decisions/0005-contained-grammar-composition.md +34 -0
- data/docs/decisions/0006-bounded-structural-grammar-lowering.md +32 -0
- data/docs/decisions/0007-shared-parser-construction-pipeline.md +33 -0
- data/docs/decisions/0008-versioned-runtime-package-boundary.md +36 -0
- data/docs/decisions/0009-isolated-parser-sessions.md +34 -0
- data/docs/decisions/0010-committed-runtime-observation.md +34 -0
- data/docs/decisions/0011-versioned-semantic-action-boundary.md +37 -0
- data/docs/decisions/0012-bounded-nonexecuting-analysis.md +37 -0
- data/docs/decisions/0013-transactional-generation-publication.md +37 -0
- data/docs/decisions/0014-versioned-generated-lexer.md +35 -0
- data/docs/decisions/0015-worker-isolated-browser-analysis.md +32 -0
- data/docs/decisions/0016-red-green-concrete-syntax.md +34 -0
- data/docs/decisions/0017-persistent-syntax-artifacts.md +35 -0
- data/docs/decisions/0018-conservative-incremental-syntax-reuse.md +38 -0
- data/docs/decisions/0019-runtime-syntax-session-boundary.md +51 -0
- data/docs/decisions/0020-grammar-ir-parser-contract.md +65 -0
- data/docs/decisions/0021-data-only-parser-table-sidecar.md +50 -0
- data/docs/decisions/0022-manifest-bound-verification-report.md +77 -0
- data/docs/decisions/0023-syntax-only-repair-results.md +48 -0
- data/docs/decisions/0024-direct-ielr-construction.md +42 -0
- data/docs/decisions/README.md +74 -0
- data/docs/declarative-configuration.md +262 -0
- data/docs/development.md +179 -2
- data/docs/direct-ielr-decision.md +120 -0
- data/docs/direct-multi-entry-decision.md +82 -0
- data/docs/editor-setup.md +2 -1
- data/docs/error-ux-review-rubric-v1.md +117 -0
- data/docs/error-ux-reviews/v1/records/README.md +22 -0
- data/docs/error-ux-round2-review-status-v1.json +27 -0
- data/docs/error-ux-round2-reviews/v1/records/README.md +30 -0
- data/docs/error-ux-round2-v1.json +1581 -0
- data/docs/error-ux-round2.md +111 -0
- data/docs/error-ux.md +54 -0
- data/docs/getting-started.md +80 -0
- data/docs/grammar-reference.md +184 -13
- data/docs/ielr-design.md +43 -0
- data/docs/ielr.md +92 -0
- data/docs/ja/bison-import.md +51 -0
- data/docs/ja/stable-api.md +53 -0
- data/docs/lexer-construction-profile.md +109 -0
- data/docs/lexer-migration.md +5 -0
- data/docs/maturity.md +128 -0
- data/docs/project-site-strategy.md +54 -0
- data/docs/racc-migration-evidence.md +45 -0
- data/docs/racc-migration.md +11 -0
- data/docs/release-readiness.md +126 -68
- data/docs/repair-semantics.md +123 -0
- data/docs/runtime-abi-evolution.md +307 -0
- data/docs/stability.md +144 -34
- data/docs/status.md +43 -0
- data/docs/syntax-sessions.md +183 -0
- data/docs/table-artifact.md +125 -0
- data/docs/test-interactions.md +219 -0
- data/docs/verification-report.md +137 -0
- data/docs/verifier-trust-boundary.md +199 -0
- data/docs/workloads.md +132 -0
- data/lib/ibex/analysis/digraph.rb +128 -0
- data/lib/ibex/analysis.rb +1 -0
- data/lib/ibex/bison_import/importer.rb +516 -0
- data/lib/ibex/bison_import/tokenizer.rb +260 -0
- data/lib/ibex/bison_import.rb +200 -0
- data/lib/ibex/bounded_subprocess.rb +171 -0
- data/lib/ibex/cli/ambiguity.rb +14 -5
- data/lib/ibex/cli/analysis.rb +134 -0
- data/lib/ibex/cli/bison_import.rb +121 -0
- data/lib/ibex/cli/config.rb +128 -0
- data/lib/ibex/cli/diagnostics.rb +31 -11
- data/lib/ibex/cli/documentation.rb +11 -8
- data/lib/ibex/cli/equiv.rb +169 -0
- data/lib/ibex/cli/error_messages.rb +11 -4
- data/lib/ibex/cli/explain.rb +14 -5
- data/lib/ibex/cli/fix.rb +203 -0
- data/lib/ibex/cli/formatting.rb +11 -10
- data/lib/ibex/cli/fuzz.rb +200 -0
- data/lib/ibex/cli/fuzz_regressions.rb +145 -0
- data/lib/ibex/cli/generation_artifacts.rb +53 -2
- data/lib/ibex/cli/generation_error_messages.rb +1 -1
- data/lib/ibex/cli/grammar_tests.rb +32 -14
- data/lib/ibex/cli/ir_tools.rb +5 -61
- data/lib/ibex/cli/outputs.rb +60 -33
- data/lib/ibex/cli/reduce.rb +202 -0
- data/lib/ibex/cli/reduce_reporting.rb +71 -0
- data/lib/ibex/cli/samples.rb +49 -23
- data/lib/ibex/cli/verify.rb +116 -0
- data/lib/ibex/cli/watch.rb +6 -5
- data/lib/ibex/cli.rb +357 -56
- data/lib/ibex/codegen/action_locations.rb +1 -1
- data/lib/ibex/codegen/action_method_source.rb +5 -6
- data/lib/ibex/codegen/action_source.rb +3 -2
- data/lib/ibex/codegen/ambiguity.rb +5 -4
- data/lib/ibex/codegen/explain.rb +123 -55
- data/lib/ibex/codegen/generated_action_abi.rb +10 -5
- data/lib/ibex/codegen/rbs.rb +15 -7
- data/lib/ibex/codegen/report.rb +25 -13
- data/lib/ibex/codegen/ruby.rb +28 -64
- data/lib/ibex/codegen/ruby_actions.rb +9 -6
- data/lib/ibex/codegen/ruby_syntax.rb +15 -9
- data/lib/ibex/configuration/analysis_grammar.rb +35 -0
- data/lib/ibex/configuration/explanation.rb +421 -0
- data/lib/ibex/configuration/inspector.rb +232 -0
- data/lib/ibex/configuration.rb +580 -0
- data/lib/ibex/coverage/collector.rb +21 -11
- data/lib/ibex/coverage/event_stream.rb +13 -7
- data/lib/ibex/coverage/report.rb +26 -18
- data/lib/ibex/coverage/runtime_event_validator.rb +30 -21
- data/lib/ibex/delta_reducer.rb +99 -0
- data/lib/ibex/diff.rb +140 -0
- data/lib/ibex/equiv/machine.rb +135 -0
- data/lib/ibex/equiv.rb +373 -0
- data/lib/ibex/fix.rb +557 -0
- data/lib/ibex/frontend/ast.rb +22 -2
- data/lib/ibex/frontend/bootstrap_parser.rb +6 -13
- data/lib/ibex/frontend/diagnostic.rb +1 -1
- data/lib/ibex/frontend/formatter.rb +72 -26
- data/lib/ibex/frontend/generated_parser.rb +147 -117
- data/lib/ibex/frontend/generated_parser_base.rb +7 -3
- data/lib/ibex/frontend/generated_parser_includes.rb +1 -11
- data/lib/ibex/frontend/generation.rb +1 -1
- data/lib/ibex/frontend/parser/declarations.rb +17 -2
- data/lib/ibex/frontend/parser_configuration_support.rb +56 -0
- data/lib/ibex/frontend/regenerator.rb +1 -0
- data/lib/ibex/frontend/resolution.rb +2 -1
- data/lib/ibex/frontend/resolver.rb +1 -1
- data/lib/ibex/frontend/rule_documentation.rb +2 -1
- data/lib/ibex/frontend/source_cursor.rb +2 -2
- data/lib/ibex/frontend/source_span.rb +9 -1
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +38 -6
- data/lib/ibex/frontend/token_adapter.rb +10 -0
- data/lib/ibex/frontend.rb +1 -0
- data/lib/ibex/fuzz.rb +219 -0
- data/lib/ibex/generation_input.rb +1 -1
- data/lib/ibex/generation_manifest.rb +36 -25
- data/lib/ibex/generation_transaction.rb +1 -1
- data/lib/ibex/generation_transaction_recovery.rb +4 -4
- data/lib/ibex/generation_transaction_validation.rb +2 -2
- data/lib/ibex/grammar_tests.rb +1 -1
- data/lib/ibex/ir/automaton_ir.rb +61 -19
- data/lib/ibex/ir/grammar_ir.rb +76 -45
- data/lib/ibex/ir/lexer_ir.rb +5 -5
- data/lib/ibex/ir/parser_contract.rb +135 -0
- data/lib/ibex/ir/serialize.rb +203 -76
- data/lib/ibex/ir/validator/automaton.rb +64 -34
- data/lib/ibex/ir/validator/base.rb +51 -33
- data/lib/ibex/ir/validator/grammar.rb +113 -83
- data/lib/ibex/ir/validator/lexer.rb +2 -2
- data/lib/ibex/ir/validator.rb +2 -1
- data/lib/ibex/ir.rb +16 -3
- data/lib/ibex/lalr/build_metrics.rb +50 -2
- data/lib/ibex/lalr/builder.rb +191 -56
- data/lib/ibex/lalr/conflict_search.rb +15 -4
- data/lib/ibex/lalr/counterexample.rb +19 -14
- data/lib/ibex/lalr/direct_lookaheads.rb +110 -57
- data/lib/ibex/lalr/goto_follows.rb +229 -0
- data/lib/ibex/lalr/ielr/annotator.rb +214 -0
- data/lib/ibex/lalr/ielr/bits.rb +28 -0
- data/lib/ibex/lalr/ielr/inadequacy.rb +47 -0
- data/lib/ibex/lalr/ielr/item_lookaheads.rb +78 -0
- data/lib/ibex/lalr/ielr/pipeline.rb +75 -0
- data/lib/ibex/lalr/ielr/split_stability.rb +78 -0
- data/lib/ibex/lalr/ielr/split_state.rb +20 -0
- data/lib/ibex/lalr/ielr/state_splitter.rb +258 -0
- data/lib/ibex/lalr/ielr_partition.rb +22 -8
- data/lib/ibex/lalr/inadequacy_report.rb +50 -0
- data/lib/ibex/lalr/lookahead_propagation.rb +111 -0
- data/lib/ibex/lalr/lr0_collection.rb +121 -0
- data/lib/ibex/lalr/unreachable_states.rb +80 -0
- data/lib/ibex/lalr.rb +52 -1
- data/lib/ibex/location.rb +3 -3
- data/lib/ibex/lsp/document_handlers.rb +9 -7
- data/lib/ibex/lsp/initialization_handlers.rb +9 -7
- data/lib/ibex/lsp/navigation_handlers.rb +25 -11
- data/lib/ibex/lsp/parser_configuration_assistance.rb +149 -0
- data/lib/ibex/lsp/position_codec.rb +6 -2
- data/lib/ibex/lsp/request_handlers.rb +3 -2
- data/lib/ibex/lsp/request_support.rb +14 -7
- data/lib/ibex/lsp/server.rb +13 -11
- data/lib/ibex/lsp/symbol_index.rb +13 -13
- data/lib/ibex/lsp/symbol_index_builder.rb +30 -20
- data/lib/ibex/lsp/symbol_occurrence.rb +8 -2
- data/lib/ibex/lsp/transport.rb +13 -3
- data/lib/ibex/lsp/workspace.rb +5 -4
- data/lib/ibex/lsp/workspace_analyzer.rb +1 -1
- data/lib/ibex/lsp.rb +1 -0
- data/lib/ibex/messages/en.yml +28 -0
- data/lib/ibex/messages/ja.yml +28 -0
- data/lib/ibex/messages.rb +68 -0
- data/lib/ibex/metrics.rb +175 -0
- data/lib/ibex/normalize/declarations.rb +2 -0
- data/lib/ibex/normalize/diagnostics.rb +63 -38
- data/lib/ibex/normalize/expander.rb +5 -4
- data/lib/ibex/normalize/expression.rb +8 -6
- data/lib/ibex/normalize/grammar_builder.rb +26 -0
- data/lib/ibex/normalize/inline_expansion.rb +100 -49
- data/lib/ibex/normalize/lexer.rb +3 -3
- data/lib/ibex/normalize/parameter_ebnf_lowering.rb +11 -10
- data/lib/ibex/normalize/parameter_lowering.rb +40 -23
- data/lib/ibex/normalize/parameters.rb +31 -4
- data/lib/ibex/normalize/parser_configuration.rb +70 -0
- data/lib/ibex/normalize.rb +10 -20
- data/lib/ibex/racc_migration/report.rb +6 -3
- data/lib/ibex/rake_task.rb +1 -1
- data/lib/ibex/samples.rb +43 -13
- data/lib/ibex/table_artifact/builder.rb +266 -0
- data/lib/ibex/table_artifact/cst_projection.rb +70 -0
- data/lib/ibex/table_artifact/document.rb +39 -0
- data/lib/ibex/table_artifact/executor.rb +187 -0
- data/lib/ibex/table_artifact/serializer.rb +57 -0
- data/lib/ibex/table_artifact/validator/metadata.rb +179 -0
- data/lib/ibex/table_artifact/validator/support.rb +86 -0
- data/lib/ibex/table_artifact/validator/tables.rb +238 -0
- data/lib/ibex/table_artifact/validator.rb +253 -0
- data/lib/ibex/table_artifact.rb +88 -0
- data/lib/ibex/table_simulation/result.rb +6 -2
- data/lib/ibex/table_simulation/step.rb +3 -1
- data/lib/ibex/tables.rb +14 -0
- data/lib/ibex/verifiable_generation_bundle.rb +86 -0
- data/lib/ibex/verification_report/builder.rb +159 -0
- data/lib/ibex/verification_report/canonical_ir.rb +123 -0
- data/lib/ibex/verification_report/logical_path.rb +68 -0
- data/lib/ibex/verification_report/validator.rb +329 -0
- data/lib/ibex/verification_report.rb +69 -0
- data/lib/ibex/verify/action_correspondence.rb +138 -0
- data/lib/ibex/verify/language_witness.rb +295 -0
- data/lib/ibex/verify/reference_collection.rb +190 -0
- data/lib/ibex/verify/result.rb +73 -0
- data/lib/ibex/verify/verifier.rb +598 -0
- data/lib/ibex/verify.rb +15 -0
- data/lib/ibex/version.rb +1 -1
- data/lib/ibex/watch/runner.rb +10 -1
- data/lib/ibex/watch/source_snapshot.rb +13 -8
- data/lib/ibex.rb +10 -0
- data/schema/{automaton-ir-v1.schema.json → automaton-ir-definitions.schema.json} +6 -6
- data/schema/{automaton-ir-v2.schema.json → automaton-ir.schema.json} +14 -7
- data/schema/bison-import-v1.schema.json +106 -0
- data/schema/conflict-explanation-review-v1.schema.json +196 -0
- data/schema/conflict-explanation-study-v1.schema.json +528 -0
- data/schema/construction-profile-v1.schema.json +779 -0
- data/schema/diff-v1.schema.json +41 -0
- data/schema/direct-ielr-decision-v1.schema.json +200 -0
- data/schema/equiv-v1.schema.json +64 -0
- data/schema/error-ux-review-v1.schema.json +609 -0
- data/schema/error-ux-round2-review-v1.schema.json +191 -0
- data/schema/error-ux-round2-v1.schema.json +519 -0
- data/schema/explain-v1.schema.json +63 -1
- data/schema/fix-v1.schema.json +64 -0
- data/schema/fix-v2.schema.json +66 -0
- data/schema/fix-v3.schema.json +101 -0
- data/schema/fuzz-regression-v1.schema.json +79 -0
- data/schema/fuzz-v1.schema.json +114 -0
- data/schema/{grammar-ir-v2.schema.json → grammar-ir-extensions.schema.json} +20 -65
- data/schema/{grammar-ir-v1.schema.json → grammar-ir-foundation.schema.json} +3 -3
- data/schema/grammar-ir.schema.json +271 -0
- data/schema/ielr-benchmark-v1.schema.json +85 -0
- data/schema/lexer-profile-v1.schema.json +491 -0
- data/schema/metrics-v1.schema.json +60 -0
- data/schema/reduce-v1.schema.json +22 -0
- data/schema/reduce-v2.schema.json +63 -0
- data/schema/table-artifact-v1.schema.json +377 -0
- data/schema/verification-report-v1.schema.json +222 -0
- data/schema/verify-v1.schema.json +42 -0
- data/sig/ibex/analysis/digraph.rbs +19 -0
- data/sig/ibex/bison_import/importer.rbs +140 -0
- data/sig/ibex/bison_import/tokenizer.rbs +91 -0
- data/sig/ibex/bison_import.rbs +95 -0
- data/sig/ibex/bounded_subprocess.rbs +76 -0
- data/sig/ibex/cli/ambiguity.rbs +6 -0
- data/sig/ibex/cli/analysis.rbs +43 -0
- data/sig/ibex/cli/bison_import.rbs +32 -0
- data/sig/ibex/cli/config.rbs +35 -0
- data/sig/ibex/cli/diagnostics.rbs +16 -3
- data/sig/ibex/cli/documentation.rbs +5 -1
- data/sig/ibex/cli/equiv.rbs +49 -0
- data/sig/ibex/cli/error_messages.rbs +6 -0
- data/sig/ibex/cli/explain.rbs +6 -0
- data/sig/ibex/cli/fix.rbs +53 -0
- data/sig/ibex/cli/formatting.rbs +5 -1
- data/sig/ibex/cli/fuzz.rbs +47 -0
- data/sig/ibex/cli/fuzz_regressions.rbs +40 -0
- data/sig/ibex/cli/generation_artifacts.rbs +21 -2
- data/sig/ibex/cli/generation_error_messages.rbs +2 -2
- data/sig/ibex/cli/grammar_tests.rbs +15 -3
- data/sig/ibex/cli/ir_tools.rbs +7 -19
- data/sig/ibex/cli/outputs.rbs +9 -3
- data/sig/ibex/cli/reduce.rbs +60 -0
- data/sig/ibex/cli/reduce_reporting.rbs +27 -0
- data/sig/ibex/cli/samples.rbs +8 -0
- data/sig/ibex/cli/verify.rbs +28 -0
- data/sig/ibex/cli/watch.rbs +2 -2
- data/sig/ibex/cli.rbs +79 -5
- data/sig/ibex/codegen/action_method_source.rbs +4 -4
- data/sig/ibex/codegen/explain.rbs +45 -25
- data/sig/ibex/codegen/generated_action_abi.rbs +14 -10
- data/sig/ibex/codegen/rbs.rbs +12 -10
- data/sig/ibex/codegen/report.rbs +12 -12
- data/sig/ibex/codegen/ruby.rbs +18 -15
- data/sig/ibex/codegen/ruby_actions.rbs +6 -6
- data/sig/ibex/codegen/ruby_syntax.rbs +10 -8
- data/sig/ibex/configuration/analysis_grammar.rbs +13 -0
- data/sig/ibex/configuration/explanation.rbs +195 -0
- data/sig/ibex/configuration/inspector.rbs +53 -0
- data/sig/ibex/configuration.rbs +242 -0
- data/sig/ibex/coverage/collector.rbs +16 -14
- data/sig/ibex/coverage/event_stream.rbs +12 -10
- data/sig/ibex/coverage/report.rbs +20 -18
- data/sig/ibex/coverage/runtime_event_validator.rbs +38 -36
- data/sig/ibex/delta_reducer.rbs +38 -0
- data/sig/ibex/diff.rbs +47 -0
- data/sig/ibex/equiv/machine.rbs +54 -0
- data/sig/ibex/equiv.rbs +129 -0
- data/sig/ibex/fix.rbs +138 -0
- data/sig/ibex/frontend/ast.rbs +33 -7
- data/sig/ibex/frontend/bootstrap_parser.rbs +2 -0
- data/sig/ibex/frontend/diagnostic.rbs +2 -2
- data/sig/ibex/frontend/formatter.rbs +38 -30
- data/sig/ibex/frontend/generated_parser.rbs +87 -77
- data/sig/ibex/frontend/generated_parser_base.rbs +4 -2
- data/sig/ibex/frontend/parser/declarations.rbs +3 -0
- data/sig/ibex/frontend/parser_configuration_support.rbs +27 -0
- data/sig/ibex/frontend/resolution.rbs +3 -2
- data/sig/ibex/frontend/source_cursor.rbs +2 -2
- data/sig/ibex/frontend/source_span.rbs +5 -2
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +13 -1
- data/sig/ibex/frontend/token_adapter.rbs +6 -0
- data/sig/ibex/fuzz.rbs +71 -0
- data/sig/ibex/generation_input.rbs +2 -2
- data/sig/ibex/generation_manifest.rbs +35 -32
- data/sig/ibex/generation_transaction_recovery.rbs +2 -2
- data/sig/ibex/generation_transaction_validation.rbs +2 -2
- data/sig/ibex/grammar_tests.rbs +2 -2
- data/sig/ibex/ir/automaton_ir.rbs +30 -11
- data/sig/ibex/ir/grammar_ir.rbs +41 -27
- data/sig/ibex/ir/lexer_ir.rbs +8 -8
- data/sig/ibex/ir/parser_contract.rbs +69 -0
- data/sig/ibex/ir/serialize.rbs +63 -28
- data/sig/ibex/ir/validator/automaton.rbs +47 -44
- data/sig/ibex/ir/validator/base.rbs +35 -31
- data/sig/ibex/ir/validator/grammar.rbs +78 -67
- data/sig/ibex/ir/validator/lexer.rbs +4 -4
- data/sig/ibex/ir.rbs +9 -3
- data/sig/ibex/lalr/build_metrics.rbs +42 -2
- data/sig/ibex/lalr/builder.rbs +75 -24
- data/sig/ibex/lalr/conflict_search.rbs +5 -2
- data/sig/ibex/lalr/counterexample.rbs +17 -14
- data/sig/ibex/lalr/direct_lookaheads.rbs +35 -28
- data/sig/ibex/lalr/goto_follows.rbs +89 -0
- data/sig/ibex/lalr/ielr/annotator.rbs +67 -0
- data/sig/ibex/lalr/ielr/bits.rbs +14 -0
- data/sig/ibex/lalr/ielr/inadequacy.rbs +24 -0
- data/sig/ibex/lalr/ielr/item_lookaheads.rbs +35 -0
- data/sig/ibex/lalr/ielr/pipeline.rbs +17 -0
- data/sig/ibex/lalr/ielr/split_stability.rbs +27 -0
- data/sig/ibex/lalr/ielr/split_state.rbs +14 -0
- data/sig/ibex/lalr/ielr/state_splitter.rbs +76 -0
- data/sig/ibex/lalr/ielr_partition.rbs +17 -5
- data/sig/ibex/lalr/inadequacy_report.rbs +21 -0
- data/sig/ibex/lalr/lookahead_propagation.rbs +38 -0
- data/sig/ibex/lalr/lr0_collection.rbs +50 -0
- data/sig/ibex/lalr/unreachable_states.rbs +25 -0
- data/sig/ibex/lalr.rbs +11 -1
- data/sig/ibex/location.rbs +6 -6
- data/sig/ibex/lsp/document_handlers.rbs +8 -8
- data/sig/ibex/lsp/initialization_handlers.rbs +12 -12
- data/sig/ibex/lsp/navigation_handlers.rbs +15 -12
- data/sig/ibex/lsp/parser_configuration_assistance.rbs +49 -0
- data/sig/ibex/lsp/position_codec.rbs +8 -4
- data/sig/ibex/lsp/request_handlers.rbs +4 -4
- data/sig/ibex/lsp/request_support.rbs +8 -8
- data/sig/ibex/lsp/server.rbs +16 -16
- data/sig/ibex/lsp/symbol_index.rbs +20 -21
- data/sig/ibex/lsp/symbol_index_builder.rbs +9 -8
- data/sig/ibex/lsp/symbol_occurrence.rbs +11 -5
- data/sig/ibex/lsp/transport.rbs +18 -6
- data/sig/ibex/lsp/workspace.rbs +3 -2
- data/sig/ibex/lsp/workspace_analyzer.rbs +1 -1
- data/sig/ibex/messages.rbs +26 -0
- data/sig/ibex/metrics.rbs +50 -0
- data/sig/ibex/normalize/diagnostics.rbs +4 -4
- data/sig/ibex/normalize/expander.rbs +2 -2
- data/sig/ibex/normalize/expression.rbs +10 -8
- data/sig/ibex/normalize/grammar_builder.rbs +11 -0
- data/sig/ibex/normalize/inline_expansion.rbs +59 -50
- data/sig/ibex/normalize/lexer.rbs +4 -4
- data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +15 -14
- data/sig/ibex/normalize/parameter_lowering.rbs +18 -18
- data/sig/ibex/normalize/parameters.rbs +13 -8
- data/sig/ibex/normalize/parser_configuration.rbs +23 -0
- data/sig/ibex/normalize.rbs +11 -5
- data/sig/ibex/racc_migration/report.rbs +8 -6
- data/sig/ibex/rake_task.rbs +1 -1
- data/sig/ibex/samples.rbs +13 -4
- data/sig/ibex/table_artifact/builder.rbs +93 -0
- data/sig/ibex/table_artifact/cst_projection.rbs +20 -0
- data/sig/ibex/table_artifact/document.rbs +27 -0
- data/sig/ibex/table_artifact/executor.rbs +66 -0
- data/sig/ibex/table_artifact/serializer.rbs +25 -0
- data/sig/ibex/table_artifact/validator/metadata.rbs +52 -0
- data/sig/ibex/table_artifact/validator/support.rbs +46 -0
- data/sig/ibex/table_artifact/validator/tables.rbs +63 -0
- data/sig/ibex/table_artifact/validator.rbs +66 -0
- data/sig/ibex/table_artifact.rbs +32 -0
- data/sig/ibex/table_simulation/result.rbs +8 -4
- data/sig/ibex/table_simulation/step.rbs +4 -2
- data/sig/ibex/tables.rbs +10 -9
- data/sig/ibex/verifiable_generation_bundle.rbs +24 -0
- data/sig/ibex/verification_report/builder.rbs +53 -0
- data/sig/ibex/verification_report/canonical_ir.rbs +41 -0
- data/sig/ibex/verification_report/logical_path.rbs +34 -0
- data/sig/ibex/verification_report/validator.rbs +114 -0
- data/sig/ibex/verification_report.rbs +44 -0
- data/sig/ibex/verify/action_correspondence.rbs +50 -0
- data/sig/ibex/verify/language_witness.rbs +112 -0
- data/sig/ibex/verify/reference_collection.rbs +63 -0
- data/sig/ibex/verify/result.rbs +52 -0
- data/sig/ibex/verify/verifier.rbs +170 -0
- data/sig/ibex/verify.rbs +6 -0
- data/sig/ibex/watch/runner.rbs +12 -2
- data/sig/ibex/watch/source_snapshot.rbs +13 -9
- metadata +232 -12
- data/lib/ibex/ir/migration.rb +0 -120
- data/sig/ibex/ir/migration.rbs +0 -34
|
@@ -19,6 +19,8 @@ module Ibex
|
|
|
19
19
|
# private def same_file_target?: (String, String) -> bool
|
|
20
20
|
# private def normalize_grammar_path: (String) -> IR::Grammar
|
|
21
21
|
# private def record_generation_input: (String, String) -> GenerationInput
|
|
22
|
+
# private def select_configuration_mode: (String) -> void
|
|
23
|
+
# private def set_configuration_option: (Symbol, Object?) -> void
|
|
22
24
|
|
|
23
25
|
private
|
|
24
26
|
|
|
@@ -61,9 +63,9 @@ module Ibex
|
|
|
61
63
|
options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
|
|
62
64
|
@options[:from] = value
|
|
63
65
|
end
|
|
64
|
-
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value|
|
|
66
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| select_configuration_mode(value) }
|
|
65
67
|
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
|
|
66
|
-
|
|
68
|
+
set_configuration_option(:algorithm, value.to_sym)
|
|
67
69
|
@options[:messages_algorithm_explicit] = true
|
|
68
70
|
end
|
|
69
71
|
options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
|
|
@@ -80,6 +82,7 @@ module Ibex
|
|
|
80
82
|
|
|
81
83
|
report_status("reading #{input_path}")
|
|
82
84
|
grammar = normalize_grammar_path(input_path)
|
|
85
|
+
grammar = activate_analysis_grammar(grammar)
|
|
83
86
|
handle_grammar_warnings(grammar, input_path)
|
|
84
87
|
build_automaton(grammar, input_path)
|
|
85
88
|
end
|
|
@@ -95,14 +98,14 @@ module Ibex
|
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
grammar = if value.is_a?(IR::Grammar)
|
|
98
|
-
value
|
|
101
|
+
activate_analysis_grammar(value)
|
|
99
102
|
elsif value.is_a?(IR::Automaton)
|
|
100
103
|
value.grammar
|
|
101
104
|
else
|
|
102
105
|
raise Ibex::Error, "#{input_path}:1:1: expected #{@options[:from]} input"
|
|
103
106
|
end
|
|
104
107
|
handle_grammar_warnings(grammar, input_path)
|
|
105
|
-
return build_automaton(
|
|
108
|
+
return build_automaton(grammar, input_path) if value.is_a?(IR::Grammar)
|
|
106
109
|
|
|
107
110
|
if value.is_a?(IR::Automaton)
|
|
108
111
|
prepare_loaded_automaton(value, input_path)
|
|
@@ -173,5 +176,9 @@ module Ibex
|
|
|
173
176
|
|
|
174
177
|
0o666 & ~File.umask
|
|
175
178
|
end
|
|
179
|
+
|
|
180
|
+
# @rbs!
|
|
181
|
+
# private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
|
|
182
|
+
# ?explicit_keys: Array[Symbol]) -> IR::Grammar
|
|
176
183
|
end
|
|
177
184
|
end
|
data/lib/ibex/cli/explain.rb
CHANGED
|
@@ -12,6 +12,8 @@ module Ibex
|
|
|
12
12
|
# private def input_path: (Array[String]) -> String
|
|
13
13
|
# private def positive_counterexample_limit: (Integer, String) -> Integer
|
|
14
14
|
# private def normalize_grammar_path: (String) -> IR::Grammar
|
|
15
|
+
# private def configuration_value: (String) -> untyped
|
|
16
|
+
# private def set_configuration_option: (Symbol, Object?) -> void
|
|
15
17
|
|
|
16
18
|
private
|
|
17
19
|
|
|
@@ -22,9 +24,10 @@ module Ibex
|
|
|
22
24
|
return print_help(parser) if @options[:help]
|
|
23
25
|
|
|
24
26
|
path = input_path(remaining)
|
|
25
|
-
grammar = normalize_grammar_path(path)
|
|
27
|
+
grammar = activate_analysis_grammar(normalize_grammar_path(path))
|
|
26
28
|
automaton = LALR::Builder.new(
|
|
27
|
-
grammar, algorithm:
|
|
29
|
+
grammar, algorithm: configuration_value("parser.algorithm"),
|
|
30
|
+
entry_isolation: configuration_value("parser.entries") == :isolated
|
|
28
31
|
).build
|
|
29
32
|
explanation = Codegen::Explain.new(
|
|
30
33
|
automaton, state: @options[:explain_state], token: @options[:explain_token],
|
|
@@ -48,12 +51,14 @@ module Ibex
|
|
|
48
51
|
@options[:explain_format] = value
|
|
49
52
|
end
|
|
50
53
|
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
|
|
51
|
-
|
|
54
|
+
set_configuration_option(:algorithm, value.to_sym)
|
|
52
55
|
end
|
|
53
56
|
options.on("--entry-isolation", "build independent state sets for each start symbol") do
|
|
54
|
-
|
|
57
|
+
set_configuration_option(:entry_isolation, true)
|
|
58
|
+
end
|
|
59
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
|
|
60
|
+
set_configuration_option(:mode, value.to_sym)
|
|
55
61
|
end
|
|
56
|
-
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
|
|
57
62
|
add_explain_search_options(options)
|
|
58
63
|
options.on("--help", "show help") { @options[:help] = true }
|
|
59
64
|
end
|
|
@@ -72,5 +77,9 @@ module Ibex
|
|
|
72
77
|
)
|
|
73
78
|
end
|
|
74
79
|
end
|
|
80
|
+
|
|
81
|
+
# @rbs!
|
|
82
|
+
# private def activate_analysis_grammar: (IR::Grammar, ?options: Hash[Symbol, Object?],
|
|
83
|
+
# ?explicit_keys: Array[Symbol]) -> IR::Grammar
|
|
75
84
|
end
|
|
76
85
|
end
|
data/lib/ibex/cli/fix.rb
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "optparse"
|
|
6
|
+
require_relative "../fix"
|
|
7
|
+
|
|
8
|
+
module Ibex
|
|
9
|
+
# CLI entry point for bounded conflict-repair proposals.
|
|
10
|
+
# rubocop:disable Metrics/ModuleLength -- CLI option wiring and output projections share one closed contract.
|
|
11
|
+
module CLIFix
|
|
12
|
+
# @rbs!
|
|
13
|
+
# type fix_options = Hash[Symbol, untyped]
|
|
14
|
+
# private def normalize_grammar_path: (String) -> IR::Grammar
|
|
15
|
+
# private def activate_cli_feature: (Symbol) -> void
|
|
16
|
+
# private def configuration_value: (String) -> untyped
|
|
17
|
+
# private def set_configuration_option: (Symbol, Object?) -> void
|
|
18
|
+
# private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
|
|
19
|
+
# private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
24
|
+
def run_fix_command(arguments)
|
|
25
|
+
settings = fix_options(arguments)
|
|
26
|
+
if settings[:help]
|
|
27
|
+
@stdout.puts(settings.fetch(:help))
|
|
28
|
+
return 0
|
|
29
|
+
end
|
|
30
|
+
paths = settings.fetch(:paths)
|
|
31
|
+
raise Ibex::Error, "(fix):1:1: fix requires exactly one grammar source file" unless paths.length == 1
|
|
32
|
+
|
|
33
|
+
path = paths.fetch(0)
|
|
34
|
+
source, fixer = prepare_fixer(path, settings)
|
|
35
|
+
report = fixer.run
|
|
36
|
+
apply_fix!(path, source, report, fixer, settings.fetch(:apply)) if settings[:apply]
|
|
37
|
+
write_fix_report(report, settings.fetch(:format))
|
|
38
|
+
proposals = report.fetch(:proposals) #: Array[Hash[Symbol, untyped]]
|
|
39
|
+
proposals.empty? ? 1 : 0
|
|
40
|
+
rescue Fix::BudgetExceeded => e
|
|
41
|
+
report = { ibex_report: "fix", schema_version: Fix::SCHEMA_VERSION }.merge(e.details)
|
|
42
|
+
write_fix_report(report, settings&.fetch(:format) || "json")
|
|
43
|
+
2
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @rbs (String path, Hash[Symbol, untyped] settings) -> [String, Fix]
|
|
47
|
+
def prepare_fixer(path, settings)
|
|
48
|
+
source = File.binread(path)
|
|
49
|
+
if BisonImport.bison_source?(source)
|
|
50
|
+
imported = BisonImport::Importer.new(source, file: path).run
|
|
51
|
+
unless imported.structurally_complete?
|
|
52
|
+
names = imported.structural_unsupported.map(&:name).uniq.sort.join(", ")
|
|
53
|
+
raise Ibex::Error,
|
|
54
|
+
"(fix):1:1: Bison import is structurally incomplete due to: #{names}"
|
|
55
|
+
end
|
|
56
|
+
raise Ibex::Error,
|
|
57
|
+
"(fix):1:1: import Bison source to a canonical analysis file before requesting source repairs"
|
|
58
|
+
end
|
|
59
|
+
grammar = normalize_grammar_path(path)
|
|
60
|
+
grammar, algorithm, automaton = fixer_construction(grammar, settings)
|
|
61
|
+
message_file = settings[:messages]
|
|
62
|
+
messages = ErrorMessages.load(message_file) if message_file
|
|
63
|
+
mode = configuration_value("grammar.mode") #: Symbol
|
|
64
|
+
fixer = Fix.new(
|
|
65
|
+
source,
|
|
66
|
+
file: path, grammar: grammar, automaton: automaton,
|
|
67
|
+
algorithm: algorithm, mode: mode,
|
|
68
|
+
state: settings[:state], conflict_index: settings[:conflict_index],
|
|
69
|
+
max_candidates: settings.fetch(:max_candidates), max_builds: settings.fetch(:max_builds),
|
|
70
|
+
equiv_samples: settings.fetch(:equiv_samples),
|
|
71
|
+
equiv_max_tokens: settings.fetch(:equiv_max_tokens),
|
|
72
|
+
equiv_max_configurations: settings.fetch(:equiv_max_configurations),
|
|
73
|
+
verify_max_states: settings.fetch(:verify_max_states),
|
|
74
|
+
verify_max_items: settings.fetch(:verify_max_items),
|
|
75
|
+
messages: messages, message_file: message_file
|
|
76
|
+
)
|
|
77
|
+
[source, fixer]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Keep command-local construction settings separate from reusable CLI state.
|
|
81
|
+
# @rbs (Array[String] arguments) -> Hash[Symbol, untyped]
|
|
82
|
+
def fix_options(arguments)
|
|
83
|
+
settings = {
|
|
84
|
+
paths: [], algorithm: Configuration::Registry.fetch("parser.algorithm").default,
|
|
85
|
+
mode: Configuration::Registry.fetch("grammar.mode").default, format: "json",
|
|
86
|
+
max_candidates: 32, max_builds: 32, equiv_samples: 100,
|
|
87
|
+
equiv_max_tokens: 8, equiv_max_configurations: 50_000,
|
|
88
|
+
verify_max_states: 100_000, verify_max_items: 1_000_000, configuration_explicit: []
|
|
89
|
+
} #: Hash[Symbol, untyped]
|
|
90
|
+
parser = OptionParser.new do |options|
|
|
91
|
+
options.banner = "Usage: ibex fix [options] GRAMMAR"
|
|
92
|
+
add_fix_target_options(options, settings)
|
|
93
|
+
add_fix_budget_options(options, settings)
|
|
94
|
+
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "current construction algorithm") do |value|
|
|
95
|
+
set_local_configuration_option(settings, :algorithm, value.to_sym)
|
|
96
|
+
end
|
|
97
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
|
|
98
|
+
set_local_configuration_option(settings, :mode, value.to_sym)
|
|
99
|
+
set_configuration_option(:mode, value.to_sym)
|
|
100
|
+
end
|
|
101
|
+
options.on("--apply[=ID]", "atomically apply one safe source proposal") do |value|
|
|
102
|
+
settings[:apply] = value || true
|
|
103
|
+
end
|
|
104
|
+
options.on("--messages=FILE", "measure effects on an error-message catalog") do |value|
|
|
105
|
+
settings[:messages] = value
|
|
106
|
+
end
|
|
107
|
+
options.on("--format=FORMAT", %w[json text], "json or text") { |value| settings[:format] = value }
|
|
108
|
+
options.on("--help", "show help") { settings[:help] = options.to_s }
|
|
109
|
+
end
|
|
110
|
+
settings[:paths] = parser.parse(arguments)
|
|
111
|
+
settings[:algorithm] = local_configuration_value(settings, "parser.algorithm")
|
|
112
|
+
settings
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @rbs (OptionParser options, Hash[Symbol, untyped] settings) -> void
|
|
116
|
+
def add_fix_target_options(options, settings)
|
|
117
|
+
options.on("--state=N", Integer, "target state (default first unresolved conflict)") do |value|
|
|
118
|
+
raise OptionParser::InvalidArgument, "--state must be nonnegative" if value.negative?
|
|
119
|
+
|
|
120
|
+
settings[:state] = value
|
|
121
|
+
end
|
|
122
|
+
options.on("--conflict-index=N", Integer, "target conflict index within the state") do |value|
|
|
123
|
+
raise OptionParser::InvalidArgument, "--conflict-index must be nonnegative" if value.negative?
|
|
124
|
+
|
|
125
|
+
settings[:conflict_index] = value
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs (OptionParser options, Hash[Symbol, untyped] settings) -> void
|
|
130
|
+
def add_fix_budget_options(options, settings)
|
|
131
|
+
{
|
|
132
|
+
"max-candidates" => :max_candidates,
|
|
133
|
+
"max-builds" => :max_builds,
|
|
134
|
+
"equiv-samples" => :equiv_samples,
|
|
135
|
+
"equiv-max-tokens" => :equiv_max_tokens,
|
|
136
|
+
"equiv-max-configurations" => :equiv_max_configurations,
|
|
137
|
+
"verify-max-states" => :verify_max_states,
|
|
138
|
+
"verify-max-items" => :verify_max_items
|
|
139
|
+
}.each do |name, key|
|
|
140
|
+
options.on("--#{name}=N", Integer, "positive bounded-search limit") do |value|
|
|
141
|
+
raise OptionParser::InvalidArgument, "--#{name} must be positive" unless value.positive?
|
|
142
|
+
|
|
143
|
+
settings[key] = value
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs (IR::Grammar grammar, Hash[Symbol, untyped] settings) -> [IR::Grammar, Symbol, IR::Automaton]
|
|
149
|
+
def fixer_construction(grammar, settings)
|
|
150
|
+
explicit_keys = settings.fetch(:configuration_explicit) & [:algorithm]
|
|
151
|
+
construct_analysis_automaton(grammar, { algorithm: settings.fetch(:algorithm) }, explicit_keys)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @rbs!
|
|
155
|
+
# private def construct_analysis_automaton: (IR::Grammar, Hash[Symbol, Object?], Array[Symbol]) ->
|
|
156
|
+
# [IR::Grammar, Symbol, IR::Automaton]
|
|
157
|
+
|
|
158
|
+
# @rbs (String path, String original, Hash[Symbol, untyped] report, Fix fixer, String | true selector) -> void
|
|
159
|
+
def apply_fix!(path, original, report, fixer, selector)
|
|
160
|
+
proposal = selected_fix_proposal(report.fetch(:proposals), selector)
|
|
161
|
+
unless proposal.fetch(:applyable)
|
|
162
|
+
raise Ibex::Error, "(fix):1:1: proposal #{proposal.fetch(:id)} changes invocation options, not source"
|
|
163
|
+
end
|
|
164
|
+
if File.symlink?(path) || File.stat(path).nlink > 1
|
|
165
|
+
raise Ibex::Error, "(fix):1:1: --apply refuses symlink aliases and files with multiple hard links"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
replacement = fixer.sources.fetch(proposal.fetch(:id))
|
|
169
|
+
activate_cli_feature(:CLIFormatting)
|
|
170
|
+
results = [{
|
|
171
|
+
path: path, label: path, source: original, formatted: replacement
|
|
172
|
+
}]
|
|
173
|
+
targets = send(:formatting_targets, results)
|
|
174
|
+
send(:transactionally_write_formatted, targets)
|
|
175
|
+
report[:applied] = proposal.fetch(:id)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs (Array[Hash[Symbol, untyped]] proposals, String | true selector) -> Hash[Symbol, untyped]
|
|
179
|
+
def selected_fix_proposal(proposals, selector)
|
|
180
|
+
proposal = if selector == true
|
|
181
|
+
proposals.find { |entry| entry.fetch(:applyable) }
|
|
182
|
+
else
|
|
183
|
+
proposals.find { |entry| entry.fetch(:id) == selector }
|
|
184
|
+
end
|
|
185
|
+
proposal || raise(Ibex::Error, "(fix):1:1: no matching applyable safe proposal")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# @rbs (Hash[Symbol, untyped] report, String format) -> void
|
|
189
|
+
def write_fix_report(report, format)
|
|
190
|
+
if format == "json"
|
|
191
|
+
@stdout.puts(JSON.pretty_generate(report))
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
@stdout.puts("result=#{report.fetch(:result)} proposals=#{report.fetch(:proposals, []).length}")
|
|
196
|
+
report.fetch(:proposals, []).each do |proposal|
|
|
197
|
+
@stdout.puts("#{proposal.fetch(:id)} #{proposal.fetch(:description)}")
|
|
198
|
+
end
|
|
199
|
+
@stdout.puts(report.fetch(:statement, Equiv::CAVEAT))
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
# rubocop:enable Metrics/ModuleLength
|
|
203
|
+
end
|
data/lib/ibex/cli/formatting.rb
CHANGED
|
@@ -9,14 +9,7 @@ module Ibex
|
|
|
9
9
|
# rubocop:disable Metrics/ModuleLength -- transaction lifecycle stays cohesive and auditable.
|
|
10
10
|
module CLIFormatting
|
|
11
11
|
# @rbs!
|
|
12
|
-
# type formatting_settings =
|
|
13
|
-
# paths: Array[String],
|
|
14
|
-
# mode: Symbol,
|
|
15
|
-
# check: bool,
|
|
16
|
-
# write: bool,
|
|
17
|
-
# ?stdin_filename: String,
|
|
18
|
-
# ?help: bool
|
|
19
|
-
# }
|
|
12
|
+
# type formatting_settings = Hash[Symbol, untyped]
|
|
20
13
|
# type formatting_result = {
|
|
21
14
|
# path: String,
|
|
22
15
|
# label: String,
|
|
@@ -40,6 +33,8 @@ module Ibex
|
|
|
40
33
|
# errors: Array[String],
|
|
41
34
|
# remaining: Array[String]
|
|
42
35
|
# }
|
|
36
|
+
# private def local_configuration_value: (Hash[Symbol, Object?], String) -> Object?
|
|
37
|
+
# private def set_local_configuration_option: (Hash[Symbol, Object?], Symbol, Object?) -> void
|
|
43
38
|
|
|
44
39
|
private
|
|
45
40
|
|
|
@@ -68,8 +63,12 @@ module Ibex
|
|
|
68
63
|
|
|
69
64
|
# @rbs (Array[String] arguments) -> formatting_settings
|
|
70
65
|
def formatting_options(arguments)
|
|
71
|
-
settings = {
|
|
66
|
+
settings = {
|
|
67
|
+
paths: [], mode: Configuration::Registry.fetch("grammar.mode").default, check: false, write: false,
|
|
68
|
+
configuration_explicit: []
|
|
69
|
+
} #: formatting_settings
|
|
72
70
|
settings[:paths] = formatting_option_parser(settings).parse(arguments)
|
|
71
|
+
settings[:mode] = local_configuration_value(settings, "grammar.mode")
|
|
73
72
|
settings
|
|
74
73
|
end
|
|
75
74
|
|
|
@@ -79,7 +78,9 @@ module Ibex
|
|
|
79
78
|
options.banner = "Usage: ibex fmt [--check | --write] [options] grammar.y [...]"
|
|
80
79
|
options.on("--check", "report files that need formatting") { settings[:check] = true }
|
|
81
80
|
options.on("--write", "atomically format files in place") { settings[:write] = true }
|
|
82
|
-
options.on("--mode=MODE", %w[default extended], "grammar mode")
|
|
81
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") do |value|
|
|
82
|
+
set_local_configuration_option(settings, :mode, value.to_sym)
|
|
83
|
+
end
|
|
83
84
|
options.on("--stdin-filename=FILE", "diagnostic filename for standard input") do |value|
|
|
84
85
|
settings[:stdin_filename] = value
|
|
85
86
|
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
require_relative "../bounded_subprocess"
|
|
7
|
+
require_relative "../fuzz"
|
|
8
|
+
require_relative "fuzz_regressions"
|
|
9
|
+
|
|
10
|
+
module Ibex
|
|
11
|
+
# CLI entry point for bounded grammar-derived differential fuzzing.
|
|
12
|
+
module CLIFuzz
|
|
13
|
+
include CLIFuzzRegressions
|
|
14
|
+
|
|
15
|
+
# @rbs!
|
|
16
|
+
# private def print_help: (OptionParser) -> Integer
|
|
17
|
+
# private def input_path: (Array[String]) -> String
|
|
18
|
+
# private def normalize_grammar_path: (String) -> IR::Grammar
|
|
19
|
+
# private def handle_grammar_warnings: (IR::Grammar, String) -> void
|
|
20
|
+
# private def warning_categories: (String) -> Array[Symbol]
|
|
21
|
+
# private def select_configuration_mode: (String) -> void
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
26
|
+
def run_fuzz_command(arguments)
|
|
27
|
+
parser = fuzz_option_parser
|
|
28
|
+
remaining = parser.parse(arguments)
|
|
29
|
+
return print_help(parser) if @options[:help]
|
|
30
|
+
|
|
31
|
+
path = input_path(remaining)
|
|
32
|
+
grammar = normalize_grammar_path(path)
|
|
33
|
+
handle_grammar_warnings(grammar, path)
|
|
34
|
+
external = external_fuzz_target
|
|
35
|
+
fuzzer = Fuzz.new(
|
|
36
|
+
grammar,
|
|
37
|
+
seed: @options.fetch(:fuzz_seed, 0), count: @options.fetch(:fuzz_count, 100),
|
|
38
|
+
max_tokens: @options.fetch(:fuzz_max_tokens, 32), max_depth: @options.fetch(:fuzz_max_depth, 16),
|
|
39
|
+
max_expansions: @options.fetch(:fuzz_max_expansions, Samples::DEFAULT_MAX_EXPANSIONS),
|
|
40
|
+
max_actions: @options.fetch(:fuzz_max_actions, Fuzz::DEFAULT_MAX_ACTIONS),
|
|
41
|
+
max_stack: @options.fetch(:fuzz_max_stack, Fuzz::DEFAULT_MAX_STACK),
|
|
42
|
+
coverage_guided: @options.fetch(:fuzz_coverage_guided, false),
|
|
43
|
+
path_length: @options.fetch(:fuzz_path_length, 2),
|
|
44
|
+
against: external&.[](0), against_description: external&.[](1)
|
|
45
|
+
)
|
|
46
|
+
write_fuzz_report(fuzzer.run)
|
|
47
|
+
0
|
|
48
|
+
rescue Fuzz::Mismatch => e
|
|
49
|
+
raise unless fuzzer && path
|
|
50
|
+
|
|
51
|
+
begin
|
|
52
|
+
write_minimized_fuzz_mismatch(fuzzer, e, path, external)
|
|
53
|
+
rescue Fuzz::BudgetExceeded => budget
|
|
54
|
+
write_fuzz_budget_report(budget, external, phase: "reduction")
|
|
55
|
+
end
|
|
56
|
+
rescue Fuzz::BudgetExceeded => e
|
|
57
|
+
write_fuzz_budget_report(e, external, phase: "comparison")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs () -> OptionParser
|
|
61
|
+
def fuzz_option_parser
|
|
62
|
+
OptionParser.new do |options|
|
|
63
|
+
options.banner = "Usage: ibex fuzz [options] grammarfile"
|
|
64
|
+
add_fuzz_generation_options(options)
|
|
65
|
+
add_fuzz_execution_options(options)
|
|
66
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| select_configuration_mode(value) }
|
|
67
|
+
options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
|
|
68
|
+
@options[:warnings] = warning_categories(value)
|
|
69
|
+
end
|
|
70
|
+
options.on("--help", "show help") { @options[:help] = true }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs (OptionParser options) -> void
|
|
75
|
+
def add_fuzz_generation_options(options)
|
|
76
|
+
options.on("--count=N", Integer, "generated sentences (default 100)") do |value|
|
|
77
|
+
@options[:fuzz_count] = positive_fuzz_option!("count", value)
|
|
78
|
+
end
|
|
79
|
+
options.on("--seed=N", Integer, "deterministic seed (default 0)") { |value| @options[:fuzz_seed] = value }
|
|
80
|
+
options.on("--max-tokens=N", Integer, "maximum generated tokens") do |value|
|
|
81
|
+
@options[:fuzz_max_tokens] = positive_fuzz_option!("max-tokens", value)
|
|
82
|
+
end
|
|
83
|
+
options.on("--max-depth=N", Integer, "maximum random expansion depth") do |value|
|
|
84
|
+
@options[:fuzz_max_depth] = positive_fuzz_option!("max-depth", value)
|
|
85
|
+
end
|
|
86
|
+
options.on("--max-expansions=N", Integer, "total derivation work budget") do |value|
|
|
87
|
+
@options[:fuzz_max_expansions] = positive_fuzz_option!("max-expansions", value)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs (OptionParser options) -> void
|
|
92
|
+
def add_fuzz_execution_options(options)
|
|
93
|
+
options.on("--max-actions=N", Integer, "actions per simulated sentence") do |value|
|
|
94
|
+
@options[:fuzz_max_actions] = positive_fuzz_option!("max-actions", value)
|
|
95
|
+
end
|
|
96
|
+
options.on("--max-stack=N", Integer, "simulated parser stack depth") do |value|
|
|
97
|
+
@options[:fuzz_max_stack] = positive_fuzz_option!("max-stack", value)
|
|
98
|
+
end
|
|
99
|
+
options.on("--coverage-guided", "prefer uncovered production paths") do
|
|
100
|
+
@options[:fuzz_coverage_guided] = true
|
|
101
|
+
end
|
|
102
|
+
options.on("--path-length=N", Integer, "coverage path length: 1 or 2") do |value|
|
|
103
|
+
raise OptionParser::InvalidArgument, "--path-length must be 1 or 2" unless [1, 2].include?(value)
|
|
104
|
+
|
|
105
|
+
@options[:fuzz_path_length] = value
|
|
106
|
+
end
|
|
107
|
+
add_fuzz_external_options(options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @rbs (OptionParser options) -> void
|
|
111
|
+
def add_fuzz_external_options(options)
|
|
112
|
+
options.on("--against=COMMAND", "compare with an explicit JSON-token subprocess") do |value|
|
|
113
|
+
@options[:fuzz_against] = value
|
|
114
|
+
end
|
|
115
|
+
options.on("--against-runtime=DESCRIPTION", "required target runtime configuration for --against") do |value|
|
|
116
|
+
raise OptionParser::InvalidArgument, "--against-runtime must not be empty" if value.empty?
|
|
117
|
+
|
|
118
|
+
@options[:fuzz_against_runtime] = value
|
|
119
|
+
end
|
|
120
|
+
options.on("--against-timeout=SECONDS", Integer, "target timeout per sentence (default 10)") do |value|
|
|
121
|
+
@options[:fuzz_against_timeout] = positive_fuzz_option!("against-timeout", value)
|
|
122
|
+
end
|
|
123
|
+
options.on("--against-max-output=N", Integer, "target stdout/stderr byte budget") do |value|
|
|
124
|
+
@options[:fuzz_against_max_output] = positive_fuzz_option!("against-max-output", value)
|
|
125
|
+
end
|
|
126
|
+
options.on("--max-reduction-trials=N", Integer, "automatic mismatch-reduction trial budget") do |value|
|
|
127
|
+
@options[:fuzz_max_reduction_trials] = positive_fuzz_option!("max-reduction-trials", value)
|
|
128
|
+
end
|
|
129
|
+
options.on("--regression-dir=DIR", "saved minimized regressions (default test/fuzz/regressions)") do |value|
|
|
130
|
+
raise OptionParser::InvalidArgument, "--regression-dir must not be empty" if value.empty?
|
|
131
|
+
|
|
132
|
+
@options[:fuzz_regression_dir] = value
|
|
133
|
+
end
|
|
134
|
+
options.on("--[no-]save-regression", "persist minimized mismatches (default enabled)") do |value|
|
|
135
|
+
@options[:fuzz_save_regression] = value
|
|
136
|
+
end
|
|
137
|
+
options.on("--format=FORMAT", %w[json text], "json or text (default json)") do |value|
|
|
138
|
+
@options[:fuzz_format] = value
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @rbs () -> fuzz_external_target?
|
|
143
|
+
def external_fuzz_target
|
|
144
|
+
command = @options[:fuzz_against]
|
|
145
|
+
runtime = @options[:fuzz_against_runtime]
|
|
146
|
+
if command.nil?
|
|
147
|
+
raise OptionParser::InvalidArgument, "--against-runtime requires --against" if runtime
|
|
148
|
+
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
unless runtime
|
|
152
|
+
raise OptionParser::InvalidArgument,
|
|
153
|
+
"--against requires --against-runtime so the comparison target is reproducible"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
arguments = Shellwords.split(command)
|
|
157
|
+
raise OptionParser::InvalidArgument, "--against command must not be empty" if arguments.empty?
|
|
158
|
+
|
|
159
|
+
timeout = @options.fetch(:fuzz_against_timeout, BoundedSubprocess::DEFAULT_TIMEOUT_SECONDS)
|
|
160
|
+
max_output = @options.fetch(:fuzz_against_max_output, BoundedSubprocess::DEFAULT_MAX_OUTPUT_BYTES)
|
|
161
|
+
subprocess = BoundedSubprocess.new(timeout_seconds: timeout, max_output_bytes: max_output)
|
|
162
|
+
runner = lambda do |tokens|
|
|
163
|
+
external_fuzz_outcome(subprocess, arguments, tokens, timeout, max_output)
|
|
164
|
+
end #: ^(Array[String]) -> Symbol
|
|
165
|
+
description = {
|
|
166
|
+
command: command, target_runtime: runtime,
|
|
167
|
+
timeout_seconds: timeout, max_output_bytes: max_output,
|
|
168
|
+
host_ruby_engine: RUBY_ENGINE, host_ruby_version: RUBY_VERSION,
|
|
169
|
+
host_platform: RUBY_PLATFORM
|
|
170
|
+
} #: fuzz_external_description
|
|
171
|
+
[runner, description]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @rbs (BoundedSubprocess subprocess, Array[String] arguments, Array[String] tokens,
|
|
175
|
+
# Integer timeout, Integer max_output) -> Symbol
|
|
176
|
+
def external_fuzz_outcome(subprocess, arguments, tokens, timeout, max_output)
|
|
177
|
+
result = subprocess.run(arguments, input: JSON.generate(tokens))
|
|
178
|
+
if result.timed_out || result.output_limited
|
|
179
|
+
raise Fuzz::BudgetExceeded.new(
|
|
180
|
+
message: "external target exceeded its #{result.timed_out ? 'timeout' : 'output limit'}",
|
|
181
|
+
bounds: { timeout_seconds: timeout, max_output_bytes: max_output }
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
unless result.status.exited? && [0, 1].include?(result.status.exitstatus)
|
|
185
|
+
detail = result.stderr.empty? ? result.stdout : result.stderr
|
|
186
|
+
raise Ibex::Error,
|
|
187
|
+
"(fuzz):1:1: external target did not exit with 0 or 1: #{detail}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
result.status.success? ? :accepted : :error
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @rbs (String name, Integer value) -> Integer
|
|
194
|
+
def positive_fuzz_option!(name, value)
|
|
195
|
+
return value if value.positive?
|
|
196
|
+
|
|
197
|
+
raise OptionParser::InvalidArgument, "--#{name} must be positive"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|