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.
- checksums.yaml +4 -4
- data/README.md +100 -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 +274 -0
- data/docs/development.md +196 -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 +1617 -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/impact.md +101 -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 +129 -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 +310 -0
- data/docs/stability.md +145 -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/sets.rb +14 -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/impact.rb +393 -0
- 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 +362 -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 +141 -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/impact/action_impact.rb +148 -0
- data/lib/ibex/impact/automaton_impact.rb +57 -0
- data/lib/ibex/impact/coverage_impact.rb +53 -0
- data/lib/ibex/impact/graph.rb +132 -0
- data/lib/ibex/impact/propagation.rb +147 -0
- data/lib/ibex/impact/report.rb +273 -0
- data/lib/ibex/impact/seeds.rb +69 -0
- data/lib/ibex/impact/severity.rb +139 -0
- data/lib/ibex/impact.rb +16 -0
- 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 +11 -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/impact-v1.schema.json +208 -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/analysis/sets.rbs +10 -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/impact.rbs +95 -0
- 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/impact/action_impact.rbs +58 -0
- data/sig/ibex/impact/automaton_impact.rbs +37 -0
- data/sig/ibex/impact/coverage_impact.rbs +28 -0
- data/sig/ibex/impact/graph.rbs +67 -0
- data/sig/ibex/impact/propagation.rbs +56 -0
- data/sig/ibex/impact/report.rbs +107 -0
- data/sig/ibex/impact/seeds.rbs +36 -0
- data/sig/ibex/impact/severity.rbs +55 -0
- data/sig/ibex/impact.rbs +6 -0
- 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 +254 -12
- data/lib/ibex/ir/migration.rb +0 -120
- data/sig/ibex/ir/migration.rbs +0 -34
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Verify
|
|
6
|
+
# Checks Automaton IR semantics against an independently derived collection.
|
|
7
|
+
# rubocop:disable Metrics/ClassLength -- V1-V9 share one violation and item-identity model.
|
|
8
|
+
class Verifier
|
|
9
|
+
DEFAULT_CHECKS = %w[V1 V3 V4 V6 V7 V8].freeze #: Array[String]
|
|
10
|
+
STRICT_CHECKS = %w[V2 V5 V9].freeze #: Array[String]
|
|
11
|
+
ERROR_ACTION = { type: :error }.freeze #: IR::error_action
|
|
12
|
+
|
|
13
|
+
# @rbs (IR::Automaton automaton, ?strict: bool, ?max_states: Integer, ?max_items: Integer) -> void
|
|
14
|
+
def initialize(automaton, strict: false, max_states: 100_000, max_items: 1_000_000)
|
|
15
|
+
@automaton = automaton
|
|
16
|
+
@grammar = automaton.grammar
|
|
17
|
+
@strict = strict
|
|
18
|
+
@max_states = max_states
|
|
19
|
+
@max_items = max_items
|
|
20
|
+
@violations = [] #: Array[Violation]
|
|
21
|
+
@sets = Analysis::Sets.new(@grammar)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @rbs () -> Result
|
|
25
|
+
def verify
|
|
26
|
+
@violations.clear
|
|
27
|
+
verify_grammar_digest
|
|
28
|
+
verify_item_collection
|
|
29
|
+
verify_transitions_and_actions
|
|
30
|
+
verify_table_formats
|
|
31
|
+
verify_reachability_and_productivity
|
|
32
|
+
verify_epsilon_termination
|
|
33
|
+
verify_conflict_determinism
|
|
34
|
+
verify_ielr_adequacy if @strict
|
|
35
|
+
Result.new(
|
|
36
|
+
algorithm: @automaton.algorithm, strict: @strict,
|
|
37
|
+
checks: DEFAULT_CHECKS + (@strict ? STRICT_CHECKS : []),
|
|
38
|
+
violations: @violations,
|
|
39
|
+
bounds: { max_states: @max_states, max_items: @max_items }
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# V9 is a bounded semantic witness for IELR only. It deliberately does
|
|
46
|
+
# not replace V1/V2 or claim unbounded language equivalence.
|
|
47
|
+
# @rbs () -> void
|
|
48
|
+
def verify_ielr_adequacy
|
|
49
|
+
return unless @automaton.algorithm == "ielr1"
|
|
50
|
+
|
|
51
|
+
witness = LanguageWitness.new(
|
|
52
|
+
@grammar, @automaton, max_tokens: 3, max_cases: 10_000,
|
|
53
|
+
max_states: @max_states, max_items: @max_items
|
|
54
|
+
).verify
|
|
55
|
+
if witness.truncated
|
|
56
|
+
violation(
|
|
57
|
+
"V9", "$.states",
|
|
58
|
+
"bounded canonical acceptance witness exhausted its budget " \
|
|
59
|
+
"(max_tokens=#{witness.max_tokens}, max_cases=#{witness.max_cases})"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
witness.differences.each do |difference|
|
|
63
|
+
violation(
|
|
64
|
+
"V9", "$.states",
|
|
65
|
+
"canonical/target acceptance differs for entry #{difference.entry.inspect} " \
|
|
66
|
+
"and tokens #{difference.tokens.inspect}: " \
|
|
67
|
+
"canonical=#{difference.canonical}, target=#{difference.target}"
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
rescue BudgetExceeded
|
|
71
|
+
raise
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
violation("V9", "$.states", "bounded canonical acceptance witness failed: #{e.message}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @rbs () -> void
|
|
77
|
+
def verify_grammar_digest
|
|
78
|
+
require "digest"
|
|
79
|
+
actual = "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(@grammar))}"
|
|
80
|
+
return if actual == @automaton.grammar_digest
|
|
81
|
+
|
|
82
|
+
violation("V1", "$.grammar_digest", "digest does not match the embedded Grammar IR")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs () -> void
|
|
86
|
+
def verify_item_collection
|
|
87
|
+
case @automaton.algorithm
|
|
88
|
+
when "slr" then compare_slr_states(reference.build(:lr0).states.map { |state| visible_state(state) })
|
|
89
|
+
when "lalr1" then compare_expected_states(lalr_states)
|
|
90
|
+
when "lr1" then compare_expected_states(lr1_states)
|
|
91
|
+
when "ielr1" then compare_ielr_states(lalr_states)
|
|
92
|
+
else violation("V1", "$.algorithm", "unsupported algorithm #{@automaton.algorithm.inspect}")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# SLR lookaheads affect completed items only. The builder may retain
|
|
97
|
+
# propagation lookaheads on non-completed items, but they cannot select
|
|
98
|
+
# an ACTION cell and therefore are outside the SLR semantic contract.
|
|
99
|
+
# @rbs (Array[Set[Array[Integer]]] expected_states) -> void
|
|
100
|
+
def compare_slr_states(expected_states)
|
|
101
|
+
expected_by_core = expected_states.group_by { |state| state.to_a.sort }
|
|
102
|
+
matched_cores = [] #: Array[Array[Array[Integer]]]
|
|
103
|
+
@automaton.states.each do |state|
|
|
104
|
+
core = verify_slr_state(state, expected_by_core)
|
|
105
|
+
matched_cores << core if core
|
|
106
|
+
end
|
|
107
|
+
return unless @strict
|
|
108
|
+
|
|
109
|
+
expected_cores = expected_states.map { |state| state.to_a.sort }
|
|
110
|
+
return if matched_cores.sort == expected_cores.sort
|
|
111
|
+
|
|
112
|
+
violation("V2", "$.states", "SLR state collection is incomplete or contains duplicate LR(0) cores")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @rbs (IR::AutomatonState state,
|
|
116
|
+
# Hash[Array[Array[Integer]], Array[Set[Array[Integer]]]] expected_by_core) ->
|
|
117
|
+
# Array[Array[Integer]]?
|
|
118
|
+
def verify_slr_state(state, expected_by_core)
|
|
119
|
+
actual_core = state.items.map { |item| [item.production, item.dot] }.uniq.sort
|
|
120
|
+
unless expected_by_core.key?(actual_core)
|
|
121
|
+
violation("V1", state_path(state), "item core is not in the independently derived LR(0) collection")
|
|
122
|
+
return
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
state.items.each { |item| verify_slr_lookaheads(state, item) }
|
|
126
|
+
actual_core
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs (IR::AutomatonState state, IR::AutomatonItem item) -> void
|
|
130
|
+
def verify_slr_lookaheads(state, item)
|
|
131
|
+
return unless item.dot == rhs_for(item.production).length
|
|
132
|
+
|
|
133
|
+
expected = item.production.negative? ? [eof_id] : follow_ids(item.production)
|
|
134
|
+
return if item.lookaheads == expected.sort
|
|
135
|
+
|
|
136
|
+
violation("V3", state_path(state), "completed item #{item.production} has invalid SLR lookaheads")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @rbs () -> Array[Set[Array[Integer]]]
|
|
140
|
+
def lr1_states
|
|
141
|
+
canonical_lr1_states.map { |state| visible_state(state) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @rbs () -> Array[Set[Array[Integer]]]
|
|
145
|
+
def lalr_states
|
|
146
|
+
groups = canonical_lr1_states.group_by { |state| core_key(state) }
|
|
147
|
+
groups.values.map do |states|
|
|
148
|
+
merged = states.each_with_object(Set.new) { |state, result| state.each { |item| result << item } }
|
|
149
|
+
visible_state(merged)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs () -> Array[Set[Array[Integer]]]
|
|
154
|
+
def canonical_lr1_states
|
|
155
|
+
reference.build(:lr1).states
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @rbs (Array[Set[Array[Integer]]] expected_states) -> void
|
|
159
|
+
def compare_expected_states(expected_states)
|
|
160
|
+
expected_by_core = expected_states.group_by { |state| core_key(state) }
|
|
161
|
+
matched = [] #: Array[Set[Array[Integer]]]
|
|
162
|
+
@automaton.states.each do |state|
|
|
163
|
+
actual = expanded_items(state)
|
|
164
|
+
candidates = expected_by_core.fetch(core_key(actual), [])
|
|
165
|
+
expected = candidates.find { |candidate| actual.subset?(candidate) }
|
|
166
|
+
unless expected
|
|
167
|
+
violation("V1", state_path(state), "item set is not a subset of an independently derived state")
|
|
168
|
+
next
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
matched << expected
|
|
172
|
+
missing = expected - actual
|
|
173
|
+
violation("V2", state_path(state), "item set is missing #{missing.length} derived items") if
|
|
174
|
+
@strict && !missing.empty?
|
|
175
|
+
end
|
|
176
|
+
return unless @strict
|
|
177
|
+
|
|
178
|
+
expected_counts = multiset(expected_states)
|
|
179
|
+
actual_counts = multiset(matched)
|
|
180
|
+
return if expected_counts == actual_counts
|
|
181
|
+
|
|
182
|
+
violation("V2", "$.states", "state collection is incomplete or contains duplicate derived states")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @rbs (Array[Set[Array[Integer]]] expected_states) -> void
|
|
186
|
+
def compare_ielr_states(expected_states)
|
|
187
|
+
expected_by_core = expected_states.to_h { |state| [core_key(state), state] }
|
|
188
|
+
actual_by_core = {} #: Hash[Array[Array[Integer]], Set[Array[Integer]]]
|
|
189
|
+
@automaton.states.each do |state|
|
|
190
|
+
actual = expanded_items(state)
|
|
191
|
+
core = core_key(actual)
|
|
192
|
+
expected = expected_by_core[core]
|
|
193
|
+
unless expected && actual.subset?(expected)
|
|
194
|
+
violation("V1", state_path(state), "IELR state contains an item outside its canonical-core union")
|
|
195
|
+
next
|
|
196
|
+
end
|
|
197
|
+
merged = actual_by_core[core] ||= Set.new
|
|
198
|
+
actual.each { |item| merged << item }
|
|
199
|
+
end
|
|
200
|
+
return unless @strict
|
|
201
|
+
|
|
202
|
+
expected_by_core.each do |core, expected|
|
|
203
|
+
next if actual_by_core.fetch(core, Set.new) == expected
|
|
204
|
+
|
|
205
|
+
violation("V2", "$.states", "IELR partitions do not cover canonical lookaheads for core #{core.inspect}")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @rbs () -> void
|
|
210
|
+
def verify_transitions_and_actions
|
|
211
|
+
@automaton.states.each do |state|
|
|
212
|
+
verify_transition_items(state)
|
|
213
|
+
verify_state_actions(state)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# @rbs (IR::AutomatonState state) -> void
|
|
218
|
+
def verify_transition_items(state)
|
|
219
|
+
state.transitions.each do |symbol_id, target_id|
|
|
220
|
+
symbol = @grammar.symbol_by_id(symbol_id)
|
|
221
|
+
target = @automaton.states[target_id]
|
|
222
|
+
unless symbol && target
|
|
223
|
+
violation("V1", state_path(state), "transition references a missing symbol or state")
|
|
224
|
+
next
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
expected = shifted_items(state, symbol_id)
|
|
228
|
+
actual = expanded_items(target)
|
|
229
|
+
missing = expected.reject { |item| actual.include?(item) }
|
|
230
|
+
unless missing.empty?
|
|
231
|
+
violation("V1", state_path(state),
|
|
232
|
+
"transition on #{symbol.name} loses #{missing.length} shifted items")
|
|
233
|
+
end
|
|
234
|
+
if symbol.nonterminal? && state.gotos[symbol_id] != target_id
|
|
235
|
+
violation("V1", state_path(state), "nonterminal transition #{symbol.name} has no matching goto")
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @rbs (IR::AutomatonState state) -> void
|
|
241
|
+
def verify_state_actions(state)
|
|
242
|
+
candidates = action_candidates(state)
|
|
243
|
+
conflict_tokens = state.conflicts.filter_map do |conflict|
|
|
244
|
+
@grammar.symbol(conflict.fetch(:symbol))&.id
|
|
245
|
+
end
|
|
246
|
+
if state.default_action && state.default_action[:type] != :reduce
|
|
247
|
+
violation("V4", state_path(state), "default action must be a reduction")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
@grammar.terminals.each do |terminal|
|
|
251
|
+
expected = candidates.fetch(terminal.id, []).uniq
|
|
252
|
+
actual = effective_action(state, terminal.id)
|
|
253
|
+
verify_terminal_action(state, terminal, expected, actual, conflict_tokens)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @rbs (IR::AutomatonState state, IR::GrammarSymbol terminal, Array[IR::parser_action] expected,
|
|
258
|
+
# IR::parser_action actual, Array[Integer] conflict_tokens) -> void
|
|
259
|
+
def verify_terminal_action(state, terminal, expected, actual, conflict_tokens)
|
|
260
|
+
if expected.empty?
|
|
261
|
+
verify_error_cell(state, terminal, actual)
|
|
262
|
+
elsif expected.one?
|
|
263
|
+
verify_single_action(state, terminal, expected.fetch(0), actual)
|
|
264
|
+
else
|
|
265
|
+
verify_conflicted_action(state, terminal, expected, actual, conflict_tokens)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# @rbs (IR::AutomatonState state, IR::GrammarSymbol terminal, IR::parser_action actual) -> void
|
|
270
|
+
def verify_error_cell(state, terminal, actual)
|
|
271
|
+
return if actual[:type] == :error
|
|
272
|
+
|
|
273
|
+
violation("V4", state_path(state), "#{terminal.name} replaces an error cell with #{actual.inspect}")
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @rbs (IR::AutomatonState state, IR::GrammarSymbol terminal, IR::parser_action expected,
|
|
277
|
+
# IR::parser_action actual) -> void
|
|
278
|
+
def verify_single_action(state, terminal, expected, actual)
|
|
279
|
+
return if actual == expected
|
|
280
|
+
|
|
281
|
+
violation("V1", state_path(state),
|
|
282
|
+
"#{terminal.name} selects #{actual.inspect}, expected #{expected.inspect}")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# @rbs (IR::AutomatonState state, IR::GrammarSymbol terminal, Array[IR::parser_action] expected,
|
|
286
|
+
# IR::parser_action actual, Array[Integer] conflict_tokens) -> void
|
|
287
|
+
def verify_conflicted_action(state, terminal, expected, actual, conflict_tokens)
|
|
288
|
+
unless expected.include?(actual) || actual[:type] == :error
|
|
289
|
+
violation("V8", state_path(state), "#{terminal.name} selects an action outside its candidates")
|
|
290
|
+
end
|
|
291
|
+
return if conflict_tokens.include?(terminal.id)
|
|
292
|
+
|
|
293
|
+
violation("V8", state_path(state), "#{terminal.name} has multiple actions without a declared resolver")
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# @rbs () -> void
|
|
297
|
+
def verify_table_formats
|
|
298
|
+
plain = Tables.build(@automaton, format: :plain)
|
|
299
|
+
compact = Tables.build(@automaton, format: :compact)
|
|
300
|
+
@automaton.states.each do |state|
|
|
301
|
+
unless action_row(plain.actions, state.id) == action_row(compact.actions, state.id)
|
|
302
|
+
violation(@strict ? "V5" : "V4", state_path(state), "plain/compact ACTION rows differ")
|
|
303
|
+
end
|
|
304
|
+
unless goto_row(plain.gotos, state.id) == goto_row(compact.gotos, state.id)
|
|
305
|
+
violation(@strict ? "V5" : "V4", state_path(state), "plain/compact GOTO rows differ")
|
|
306
|
+
end
|
|
307
|
+
next if plain.default_actions.fetch(state.id) == compact.default_actions.fetch(state.id)
|
|
308
|
+
|
|
309
|
+
violation(@strict ? "V5" : "V4", state_path(state), "plain/compact default actions differ")
|
|
310
|
+
end
|
|
311
|
+
rescue StandardError => e
|
|
312
|
+
violation(@strict ? "V5" : "V4", "$.states", "table transformation failed: #{e.message}")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# @rbs (Tables::action_table table, Integer row) -> Hash[Integer, IR::runtime_action]
|
|
316
|
+
def action_row(table, row)
|
|
317
|
+
table.is_a?(Array) ? table.fetch(row) : table.row(row)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# @rbs (Tables::goto_table table, Integer row) -> Hash[Integer, untyped]
|
|
321
|
+
def goto_row(table, row)
|
|
322
|
+
table.is_a?(Array) ? table.fetch(row) : table.row(row)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @rbs () -> void
|
|
326
|
+
def verify_reachability_and_productivity
|
|
327
|
+
reachable = reachable_states
|
|
328
|
+
@automaton.states.each do |state|
|
|
329
|
+
unless reachable.include?(state.id)
|
|
330
|
+
violation("V6", state_path(state),
|
|
331
|
+
"state is unreachable from every entry")
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
productive = productive_nonterminals
|
|
336
|
+
@grammar.nonterminals.each do |symbol|
|
|
337
|
+
unless productive.include?(symbol.id)
|
|
338
|
+
violation("V6", "$.grammar.symbols[#{symbol.id}]",
|
|
339
|
+
"nonterminal #{symbol.name} derives no terminal sentence")
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# @rbs () -> void
|
|
345
|
+
def verify_epsilon_termination
|
|
346
|
+
@grammar.terminals.each do |terminal|
|
|
347
|
+
edges = {} #: Hash[Integer, Integer]
|
|
348
|
+
@automaton.states.each do |state|
|
|
349
|
+
action = effective_action(state, terminal.id)
|
|
350
|
+
next unless action[:type] == :reduce
|
|
351
|
+
|
|
352
|
+
production = @grammar.productions[action.fetch(:production)]
|
|
353
|
+
next unless production&.rhs&.empty?
|
|
354
|
+
|
|
355
|
+
target = state.gotos[production.lhs]
|
|
356
|
+
edges[state.id] = target if target
|
|
357
|
+
end
|
|
358
|
+
next unless directed_cycle?(edges)
|
|
359
|
+
|
|
360
|
+
violation("V7", "$.states", "epsilon reductions can cycle without consuming #{terminal.name}")
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# @rbs () -> void
|
|
365
|
+
def verify_conflict_determinism
|
|
366
|
+
@automaton.states.each do |state|
|
|
367
|
+
grouped = state.conflicts.group_by { |conflict| conflict.fetch(:symbol) }
|
|
368
|
+
grouped.each do |symbol, conflicts|
|
|
369
|
+
violation("V8", state_path(state), "#{symbol} has more than one declared resolver") if conflicts.length > 1
|
|
370
|
+
resolution = conflicts.first&.fetch(:resolution, nil)
|
|
371
|
+
unless resolution&.key?(:by) && resolution.key?(:chose)
|
|
372
|
+
violation("V8", state_path(state), "#{symbol} has an incomplete resolver")
|
|
373
|
+
next
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
verify_conflict_resolution(state, conflicts.first)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# @rbs (IR::AutomatonState state, IR::conflict conflict) -> void
|
|
382
|
+
def verify_conflict_resolution(state, conflict)
|
|
383
|
+
terminal = @grammar.symbol(conflict.fetch(:symbol))
|
|
384
|
+
unless terminal&.terminal?
|
|
385
|
+
violation("V8", state_path(state), "resolver references a missing terminal")
|
|
386
|
+
return
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
candidates = action_candidates(state).fetch(terminal.id, []).uniq
|
|
390
|
+
expected = resolved_conflict_action(conflict)
|
|
391
|
+
unless conflict_candidates(conflict).all? { |candidate| candidates.include?(candidate) }
|
|
392
|
+
violation("V8", state_path(state), "#{terminal.name} resolver names actions outside the cell")
|
|
393
|
+
end
|
|
394
|
+
return if expected && effective_action(state, terminal.id) == expected
|
|
395
|
+
|
|
396
|
+
violation("V8", state_path(state), "#{terminal.name} resolver choice does not match the ACTION cell")
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# @rbs (IR::conflict conflict) -> Array[IR::parser_action]
|
|
400
|
+
def conflict_candidates(conflict)
|
|
401
|
+
if conflict.fetch(:type).to_sym == :shift_reduce
|
|
402
|
+
shift_reduce = conflict #: IR::shift_reduce_conflict
|
|
403
|
+
[
|
|
404
|
+
{ type: :shift, state: shift_reduce.fetch(:shift_to) },
|
|
405
|
+
{ type: :reduce, production: shift_reduce.fetch(:reduce) }
|
|
406
|
+
]
|
|
407
|
+
else
|
|
408
|
+
reduce_reduce = conflict #: IR::reduce_reduce_conflict
|
|
409
|
+
reduce_reduce.fetch(:reductions).map { |production| { type: :reduce, production: production } }
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# @rbs (IR::conflict conflict) -> IR::parser_action?
|
|
414
|
+
def resolved_conflict_action(conflict)
|
|
415
|
+
chosen = conflict.fetch(:resolution).fetch(:chose)
|
|
416
|
+
return { type: :reduce, production: chosen } if chosen.is_a?(Integer) #: IR::reduce_action
|
|
417
|
+
|
|
418
|
+
shift_reduce = conflict #: IR::shift_reduce_conflict
|
|
419
|
+
case chosen.to_sym
|
|
420
|
+
when :shift then { type: :shift, state: shift_reduce.fetch(:shift_to) }
|
|
421
|
+
when :reduce then { type: :reduce, production: shift_reduce.fetch(:reduce) }
|
|
422
|
+
when :error then ERROR_ACTION
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# @rbs (IR::AutomatonState state) -> Hash[Integer, Array[IR::parser_action]]
|
|
427
|
+
def action_candidates(state)
|
|
428
|
+
candidates = Hash.new { |hash, key| hash[key] = [] } #: Hash[Integer, Array[IR::parser_action]]
|
|
429
|
+
state.transitions.each do |symbol_id, target|
|
|
430
|
+
symbol = @grammar.symbol_by_id(symbol_id)
|
|
431
|
+
candidates[symbol_id] << { type: :shift, state: target } if symbol&.terminal?
|
|
432
|
+
end
|
|
433
|
+
state.items.each do |item|
|
|
434
|
+
next unless item.dot == rhs_for(item.production).length
|
|
435
|
+
|
|
436
|
+
item.lookaheads.each do |lookahead|
|
|
437
|
+
action = if item.production.negative?
|
|
438
|
+
{ type: :accept } #: IR::accept_action
|
|
439
|
+
else
|
|
440
|
+
{ type: :reduce, production: item.production } #: IR::reduce_action
|
|
441
|
+
end
|
|
442
|
+
candidates[lookahead] << action
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
candidates
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# @rbs (IR::AutomatonState state, Integer symbol_id) -> Array[Array[Integer]]
|
|
449
|
+
def shifted_items(state, symbol_id)
|
|
450
|
+
state.items.filter_map do |item|
|
|
451
|
+
expected_symbol = rhs_for(item.production)[item.dot]
|
|
452
|
+
if item.production.negative? && item.dot.zero?
|
|
453
|
+
next unless start_symbol_ids.include?(symbol_id)
|
|
454
|
+
else
|
|
455
|
+
next unless expected_symbol == symbol_id
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
item.lookaheads.map { |lookahead| [item.production, item.dot + 1, lookahead] }
|
|
459
|
+
end.flatten(1)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# @rbs (IR::AutomatonState state, Integer terminal_id) -> IR::parser_action
|
|
463
|
+
def effective_action(state, terminal_id)
|
|
464
|
+
state.actions.fetch(terminal_id, state.default_action || ERROR_ACTION)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# @rbs () -> Set[Integer]
|
|
468
|
+
def reachable_states
|
|
469
|
+
found = Set.new
|
|
470
|
+
queue = @automaton.entry_states.values.dup
|
|
471
|
+
until queue.empty?
|
|
472
|
+
id = queue.shift
|
|
473
|
+
next if found.include?(id)
|
|
474
|
+
|
|
475
|
+
found << id
|
|
476
|
+
state = @automaton.states[id]
|
|
477
|
+
next unless state
|
|
478
|
+
|
|
479
|
+
queue.concat(state.transitions.values)
|
|
480
|
+
queue.concat(state.gotos.values)
|
|
481
|
+
state.actions.each_value { |action| queue << action[:state] if action[:type] == :shift }
|
|
482
|
+
end
|
|
483
|
+
found
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# @rbs () -> Set[Integer]
|
|
487
|
+
def productive_nonterminals
|
|
488
|
+
productive = Set.new
|
|
489
|
+
pending = @grammar.productions.dup
|
|
490
|
+
loop do
|
|
491
|
+
changed = false
|
|
492
|
+
pending.delete_if do |production|
|
|
493
|
+
ready = production.rhs.all? do |id|
|
|
494
|
+
symbol = @grammar.symbol_by_id(id)
|
|
495
|
+
symbol&.terminal? || productive.include?(id)
|
|
496
|
+
end
|
|
497
|
+
productive << production.lhs if ready
|
|
498
|
+
changed ||= ready
|
|
499
|
+
ready
|
|
500
|
+
end
|
|
501
|
+
break unless changed
|
|
502
|
+
end
|
|
503
|
+
productive
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# @rbs (Hash[Integer, Integer] edges) -> bool
|
|
507
|
+
def directed_cycle?(edges)
|
|
508
|
+
edges.each_key do |start|
|
|
509
|
+
seen = Set.new
|
|
510
|
+
cursor = start
|
|
511
|
+
while cursor && edges.key?(cursor)
|
|
512
|
+
return true if seen.include?(cursor)
|
|
513
|
+
|
|
514
|
+
seen << cursor
|
|
515
|
+
cursor = edges[cursor]
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
false
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# @rbs (IR::AutomatonState state) -> Set[Array[Integer]]
|
|
522
|
+
def expanded_items(state)
|
|
523
|
+
state.items.each_with_object(Set.new) do |item, result|
|
|
524
|
+
item.lookaheads.each { |lookahead| result << [item.production, item.dot, lookahead] }
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# Automaton IR intentionally exposes every augmented production as -1,
|
|
529
|
+
# even when construction internally assigned one per start symbol.
|
|
530
|
+
# @rbs (Set[Array[Integer]] state) -> Set[Array[Integer]]
|
|
531
|
+
def visible_state(state)
|
|
532
|
+
state.each_with_object(Set.new) do |item, result|
|
|
533
|
+
production = item.fetch(0)
|
|
534
|
+
dot = item.fetch(1)
|
|
535
|
+
lookahead = item[2]
|
|
536
|
+
result << [production.negative? ? -1 : production, dot, lookahead].compact
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# @rbs (Set[Array[Integer]] state) -> Array[Array[Integer]]
|
|
541
|
+
def core_key(state)
|
|
542
|
+
state.map { |item| [item.fetch(0), item.fetch(1)] }.uniq.sort
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# @rbs (Array[Set[Array[Integer]]] states) -> Hash[Array[Array[Integer]], Integer]
|
|
546
|
+
def multiset(states)
|
|
547
|
+
states.each_with_object(Hash.new(0)) { |state, counts| counts[state.to_a.sort] += 1 }
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# @rbs (Integer production_id) -> Array[Integer]
|
|
551
|
+
def follow_ids(production_id)
|
|
552
|
+
production = @grammar.productions.fetch(production_id)
|
|
553
|
+
names = @sets.follow(production.lhs)
|
|
554
|
+
names.map { |name| @grammar.symbol(name)&.id }.compact
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# @rbs (Integer production_id) -> Array[Integer]
|
|
558
|
+
def rhs_for(production_id)
|
|
559
|
+
return [start_symbol(production_id).id] if production_id.negative?
|
|
560
|
+
|
|
561
|
+
production = @grammar.productions[production_id]
|
|
562
|
+
production&.rhs || []
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# @rbs (Integer production_id) -> IR::GrammarSymbol
|
|
566
|
+
def start_symbol(production_id)
|
|
567
|
+
name = @grammar.starts.fetch(-production_id - 1)
|
|
568
|
+
@grammar.symbol(name) || raise(Ibex::Error, "(verify):1:1: missing start symbol #{name}")
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# @rbs () -> Integer
|
|
572
|
+
def eof_id
|
|
573
|
+
@grammar.symbol("$eof")&.id || raise(Ibex::Error, "(verify):1:1: grammar has no $eof terminal")
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# @rbs () -> Array[Integer]
|
|
577
|
+
def start_symbol_ids
|
|
578
|
+
@start_symbol_ids ||= @grammar.starts.map do |name|
|
|
579
|
+
@grammar.symbol(name)&.id || raise(Ibex::Error, "(verify):1:1: missing start symbol #{name}")
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# @rbs () -> ReferenceCollection
|
|
584
|
+
def reference
|
|
585
|
+
@reference ||= ReferenceCollection.new(@grammar, max_states: @max_states, max_items: @max_items)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# @rbs (String id, String location, String message) -> void
|
|
589
|
+
def violation(id, location, message)
|
|
590
|
+
@violations << Violation.new(id: id, location: location, message: message).freeze
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# @rbs (IR::AutomatonState state) -> String
|
|
594
|
+
def state_path(state) = "$.states[#{state.id}]"
|
|
595
|
+
end
|
|
596
|
+
# rubocop:enable Metrics/ClassLength
|
|
597
|
+
end
|
|
598
|
+
end
|
data/lib/ibex/verify.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "analysis"
|
|
5
|
+
require_relative "tables"
|
|
6
|
+
require_relative "verify/reference_collection"
|
|
7
|
+
require_relative "verify/result"
|
|
8
|
+
require_relative "verify/verifier"
|
|
9
|
+
require_relative "verify/action_correspondence"
|
|
10
|
+
require_relative "verify/language_witness"
|
|
11
|
+
|
|
12
|
+
module Ibex
|
|
13
|
+
module Verify
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/ibex/version.rb
CHANGED
data/lib/ibex/watch/runner.rb
CHANGED
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
module Ibex
|
|
5
5
|
module Watch
|
|
6
|
+
# @rbs!
|
|
7
|
+
# interface _BuildResult
|
|
8
|
+
# def source_paths: () -> Array[String]
|
|
9
|
+
# def attempted_paths: () -> Array[String]
|
|
10
|
+
# end
|
|
11
|
+
# interface _Output
|
|
12
|
+
# def puts: (*Object?) -> Object?
|
|
13
|
+
# end
|
|
14
|
+
|
|
6
15
|
# Polls source fingerprints, debounces changes, and publishes stable builds.
|
|
7
16
|
class Runner
|
|
8
17
|
DEFAULT_INTERVAL = 0.25 #: Float
|
|
9
18
|
DEFAULT_DEBOUNCE = 0.05 #: Float
|
|
10
19
|
|
|
11
20
|
# rubocop:disable Layout/LineLength
|
|
12
|
-
# @rbs (paths: Array[String], build: ^() ->
|
|
21
|
+
# @rbs (paths: Array[String], build: ^() -> _BuildResult, publish: ^(_BuildResult, SourceSnapshot, ^() -> bool) -> void, failure_paths: ^() -> Array[String], stderr: _Output, clock: ^() -> Float, sleeper: ^(Float) -> void, iteration_hook: ^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil), ?interval: Float, ?debounce: Float) -> void
|
|
13
22
|
def initialize(paths:, build:, publish:, failure_paths:, stderr:, clock:, sleeper:, iteration_hook:,
|
|
14
23
|
interval: DEFAULT_INTERVAL, debounce: DEFAULT_DEBOUNCE)
|
|
15
24
|
@fixed_paths = normalize_paths(paths)
|