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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "validator/support"
|
|
4
|
+
require_relative "validator/tables"
|
|
5
|
+
require_relative "validator/metadata"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
module TableArtifact
|
|
9
|
+
# Closed structural and referential validation for untrusted sidecars.
|
|
10
|
+
class Validator
|
|
11
|
+
include ValidationSupport
|
|
12
|
+
|
|
13
|
+
ROOT_KEYS = %w[artifact_type schema_version identity payload cost].freeze #: Array[String]
|
|
14
|
+
PAYLOAD_KEYS = %w[
|
|
15
|
+
table_format source state_count symbols tokens entry_states tables productions cst recovery semantic_actions
|
|
16
|
+
].freeze #: Array[String]
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# @rbs (Hash[String, ValidationSupport::json_value] data) -> true
|
|
20
|
+
def validate!(data)
|
|
21
|
+
new(data).validate!
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs (Hash[String, ValidationSupport::json_value] data) -> void
|
|
26
|
+
def initialize(data)
|
|
27
|
+
@data = data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# rubocop:disable Naming/PredicateMethod -- bang denotes fail-fast validation.
|
|
31
|
+
# @rbs () -> true
|
|
32
|
+
def validate!
|
|
33
|
+
record(@data, "$", ROOT_KEYS)
|
|
34
|
+
payload = record(@data.fetch("payload"), "$.payload", PAYLOAD_KEYS)
|
|
35
|
+
validate_root_identity
|
|
36
|
+
representation = validate_table_format(payload.fetch("table_format"))
|
|
37
|
+
validate_source(payload.fetch("source"))
|
|
38
|
+
state_count = integer(payload.fetch("state_count"), "$.payload.state_count", minimum: 1)
|
|
39
|
+
symbols = validate_symbols(payload.fetch("symbols"))
|
|
40
|
+
validate_tokens(payload.fetch("tokens"), symbols)
|
|
41
|
+
validate_entries(payload.fetch("entry_states"), symbols, state_count)
|
|
42
|
+
productions = validate_productions(payload.fetch("productions"), symbols)
|
|
43
|
+
tables = payload.fetch("tables") #: Hash[String, ValidationSupport::json_value]
|
|
44
|
+
TableValidator.new(
|
|
45
|
+
tables,
|
|
46
|
+
state_count: state_count,
|
|
47
|
+
production_count: productions.length,
|
|
48
|
+
terminal_ids: symbol_ids(symbols, "terminal"),
|
|
49
|
+
nonterminal_ids: symbol_ids(symbols, "nonterminal"),
|
|
50
|
+
representation: representation
|
|
51
|
+
).validate!
|
|
52
|
+
MetadataValidator.new(payload, symbols: symbols, productions: productions).validate!
|
|
53
|
+
validate_cost(@data.fetch("cost"), payload)
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
# rubocop:enable Naming/PredicateMethod
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# @rbs () -> void
|
|
61
|
+
def validate_root_identity
|
|
62
|
+
invalid("$.artifact_type", "is unsupported") unless @data.fetch("artifact_type") == ARTIFACT_TYPE
|
|
63
|
+
invalid("$.schema_version", "is unsupported") unless @data.fetch("schema_version") == SCHEMA_VERSION
|
|
64
|
+
identity_keys = %w[
|
|
65
|
+
grammar_digest automaton_digest payload_digest cst_metadata_digest recovery_metadata_digest
|
|
66
|
+
]
|
|
67
|
+
identity = record(@data.fetch("identity"), "$.identity", identity_keys)
|
|
68
|
+
identity.each { |key, value| digest(value, "$.identity.#{key}") }
|
|
69
|
+
expected = Serializer.digest(@data.fetch("payload"))
|
|
70
|
+
invalid("$.identity.payload_digest", "does not match the canonical payload") unless
|
|
71
|
+
identity.fetch("payload_digest") == expected
|
|
72
|
+
payload = @data.fetch("payload")
|
|
73
|
+
validate_named_digest(identity, payload, "cst_metadata", "cst")
|
|
74
|
+
validate_named_digest(identity, payload, "recovery_metadata", "recovery")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs (ValidationSupport::json_value data) -> String
|
|
78
|
+
def validate_table_format(data)
|
|
79
|
+
path = "$.payload.table_format"
|
|
80
|
+
keys = %w[family version representation action_encoding goto_encoding]
|
|
81
|
+
data = record(data, path, keys)
|
|
82
|
+
enum(data.fetch("family"), "#{path}.family", ["ibex-runtime-parser-table"])
|
|
83
|
+
invalid("#{path}.version", "must be 6") unless integer(data.fetch("version"), "#{path}.version") == 6
|
|
84
|
+
representation = enum(data.fetch("representation"), "#{path}.representation", %w[plain compact]) #: String
|
|
85
|
+
expected = if representation == "compact"
|
|
86
|
+
%w[signed-row-displacement-v1 row-displacement-v1]
|
|
87
|
+
else
|
|
88
|
+
%w[signed-sparse-rows-v1 sparse-rows-v1]
|
|
89
|
+
end
|
|
90
|
+
actual = [data.fetch("action_encoding"), data.fetch("goto_encoding")]
|
|
91
|
+
invalid(path, "encodings do not match representation") unless actual == expected
|
|
92
|
+
representation
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @rbs (ValidationSupport::json_value data) -> void
|
|
96
|
+
def validate_source(data)
|
|
97
|
+
source = record(data, "$.payload.source", %w[grammar_digest automaton_digest])
|
|
98
|
+
source.each { |key, value| digest(value, "$.payload.source.#{key}") }
|
|
99
|
+
identity = @data.fetch("identity")
|
|
100
|
+
invalid("$.payload.source", "must match root identity") unless
|
|
101
|
+
source.fetch("grammar_digest") == identity.fetch("grammar_digest") &&
|
|
102
|
+
source.fetch("automaton_digest") == identity.fetch("automaton_digest")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (ValidationSupport::json_value data) -> Array[Hash[String, ValidationSupport::json_value]]
|
|
106
|
+
def validate_symbols(data)
|
|
107
|
+
symbols = array(data, "$.payload.symbols") #: Array[Hash[String, ValidationSupport::json_value]]
|
|
108
|
+
invalid("$.payload.symbols", "must not be empty") if symbols.empty?
|
|
109
|
+
names = [] #: Array[String]
|
|
110
|
+
symbols.each_with_index do |symbol, index|
|
|
111
|
+
path = "$.payload.symbols[#{index}]"
|
|
112
|
+
record(symbol, path, %w[id name kind display_name])
|
|
113
|
+
invalid("#{path}.id", "must be contiguous and ordered") unless symbol.fetch("id") == index
|
|
114
|
+
names << string(symbol.fetch("name"), "#{path}.name")
|
|
115
|
+
enum(symbol.fetch("kind"), "#{path}.kind", %w[terminal nonterminal])
|
|
116
|
+
nullable_string(symbol.fetch("display_name"), "#{path}.display_name")
|
|
117
|
+
end
|
|
118
|
+
invalid("$.payload.symbols", "names must be unique") unless names.uniq.length == names.length
|
|
119
|
+
symbols
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @rbs (ValidationSupport::json_value data, Array[Hash[String, ValidationSupport::json_value]] symbols) -> void
|
|
123
|
+
def validate_tokens(data, symbols)
|
|
124
|
+
tokens = array(data, "$.payload.tokens") #: Array[Hash[String, ValidationSupport::json_value]]
|
|
125
|
+
expected = symbols.select { |symbol| symbol.fetch("kind") == "terminal" }.map do |symbol|
|
|
126
|
+
symbol.slice("id", "name", "display_name")
|
|
127
|
+
end
|
|
128
|
+
tokens.each_with_index do |token, index|
|
|
129
|
+
record(token, "$.payload.tokens[#{index}]", %w[id name display_name])
|
|
130
|
+
end
|
|
131
|
+
invalid("$.payload.tokens", "must exactly index terminal symbols") unless tokens == expected
|
|
132
|
+
token_id = tokens.first&.fetch("id")
|
|
133
|
+
invalid("$.payload.tokens", "token id 0 must be $eof") unless token_id.is_a?(Integer) && token_id.zero? &&
|
|
134
|
+
tokens.first.fetch("name") == "$eof"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @rbs (ValidationSupport::json_value data,
|
|
138
|
+
# Array[Hash[String, ValidationSupport::json_value]] symbols, Integer state_count) -> void
|
|
139
|
+
def validate_entries(data, symbols, state_count)
|
|
140
|
+
entries = array(data, "$.payload.entry_states") #: Array[Hash[String, ValidationSupport::json_value]]
|
|
141
|
+
invalid("$.payload.entry_states", "must not be empty") if entries.empty?
|
|
142
|
+
names = entries.map.with_index do |entry, index|
|
|
143
|
+
path = "$.payload.entry_states[#{index}]"
|
|
144
|
+
record(entry, path, %w[name state])
|
|
145
|
+
name = string(entry.fetch("name"), "#{path}.name")
|
|
146
|
+
symbol = symbols.find { |candidate| candidate.fetch("name") == name }
|
|
147
|
+
invalid("#{path}.name", "must reference a nonterminal") unless symbol&.fetch("kind") == "nonterminal"
|
|
148
|
+
state = integer(entry.fetch("state"), "#{path}.state", minimum: 0)
|
|
149
|
+
invalid("#{path}.state", "references a missing state") unless state < state_count
|
|
150
|
+
name
|
|
151
|
+
end
|
|
152
|
+
invalid("$.payload.entry_states", "names must be unique") unless names.uniq.length == names.length
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @rbs (ValidationSupport::json_value data,
|
|
156
|
+
# Array[Hash[String, ValidationSupport::json_value]] symbols) -> Array[Hash[String, ValidationSupport::json_value]]
|
|
157
|
+
def validate_productions(data, symbols)
|
|
158
|
+
productions = array(data, "$.payload.productions") #: Array[Hash[String, ValidationSupport::json_value]]
|
|
159
|
+
nonterminals = symbol_ids(symbols, "nonterminal")
|
|
160
|
+
productions.each_with_index do |production, index|
|
|
161
|
+
path = "$.payload.productions[#{index}]"
|
|
162
|
+
record(production, path, %w[id lhs rhs rhs_length action_slot])
|
|
163
|
+
invalid("#{path}.id", "must be contiguous and ordered") unless production.fetch("id") == index
|
|
164
|
+
lhs = integer(production.fetch("lhs"), "#{path}.lhs", minimum: 0)
|
|
165
|
+
invalid("#{path}.lhs", "must reference a nonterminal") unless nonterminals.include?(lhs)
|
|
166
|
+
rhs = array(production.fetch("rhs"), "#{path}.rhs")
|
|
167
|
+
rhs.each_with_index do |symbol_id, rhs_index|
|
|
168
|
+
symbol_id = integer(symbol_id, "#{path}.rhs[#{rhs_index}]", minimum: 0)
|
|
169
|
+
invalid("#{path}.rhs[#{rhs_index}]", "references a missing symbol") unless symbol_id < symbols.length
|
|
170
|
+
end
|
|
171
|
+
invalid("#{path}.rhs_length", "must match rhs") unless production.fetch("rhs_length") == rhs.length
|
|
172
|
+
slot = nullable_integer(production.fetch("action_slot"), "#{path}.action_slot", minimum: 0)
|
|
173
|
+
invalid("#{path}.action_slot", "must equal production id") if slot && slot != index
|
|
174
|
+
end
|
|
175
|
+
productions
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs (ValidationSupport::json_value data, Hash[String, ValidationSupport::json_value] payload) -> void
|
|
179
|
+
def validate_cost(data, payload)
|
|
180
|
+
path = "$.cost"
|
|
181
|
+
keys = %w[
|
|
182
|
+
canonical_payload_bytes action_cells goto_cells lookup_cost recognition_cost measurement bounded_by_max_steps
|
|
183
|
+
]
|
|
184
|
+
data = record(data, path, keys)
|
|
185
|
+
bytes = integer(data.fetch("canonical_payload_bytes"), "#{path}.canonical_payload_bytes", minimum: 1)
|
|
186
|
+
unless bytes == Serializer.compact(payload).bytesize
|
|
187
|
+
invalid("#{path}.canonical_payload_bytes",
|
|
188
|
+
"does not match payload")
|
|
189
|
+
end
|
|
190
|
+
action_cells = integer(data.fetch("action_cells"), "#{path}.action_cells", minimum: 0)
|
|
191
|
+
goto_cells = integer(data.fetch("goto_cells"), "#{path}.goto_cells", minimum: 0)
|
|
192
|
+
tables = payload.fetch("tables") #: Hash[String, ValidationSupport::json_value]
|
|
193
|
+
actions = tables.fetch("actions") #: Hash[String, ValidationSupport::json_value]
|
|
194
|
+
gotos = tables.fetch("gotos") #: Hash[String, ValidationSupport::json_value]
|
|
195
|
+
invalid("#{path}.action_cells", "does not match actions") unless action_cells == occupied(actions, "codes")
|
|
196
|
+
invalid("#{path}.goto_cells", "does not match gotos") unless goto_cells == occupied(gotos, "values")
|
|
197
|
+
expected_lookup, expected_recognition = expected_costs(payload)
|
|
198
|
+
invalid("#{path}.lookup_cost", "does not match table representation") unless
|
|
199
|
+
data.fetch("lookup_cost") == expected_lookup
|
|
200
|
+
invalid("#{path}.recognition_cost", "does not match table representation") unless
|
|
201
|
+
data.fetch("recognition_cost") == expected_recognition
|
|
202
|
+
enum(data.fetch("measurement"), "#{path}.measurement", ["not-measured"])
|
|
203
|
+
invalid("#{path}.bounded_by_max_steps", "must be true") unless boolean(data.fetch("bounded_by_max_steps"),
|
|
204
|
+
"#{path}.bounded_by_max_steps")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @rbs (Hash[String, ValidationSupport::json_value] payload) -> [String, String]
|
|
208
|
+
def expected_costs(payload)
|
|
209
|
+
table_format = payload.fetch("table_format") #: Hash[String, ValidationSupport::json_value]
|
|
210
|
+
compact = table_format.fetch("representation") == "compact"
|
|
211
|
+
lookup = compact ? "O(1) row-displacement probe" : "O(log row width) binary search"
|
|
212
|
+
recognition = if compact
|
|
213
|
+
"O(input tokens + reductions)"
|
|
214
|
+
else
|
|
215
|
+
"O((input tokens + reductions) * log maximum row width)"
|
|
216
|
+
end
|
|
217
|
+
[lookup, "#{recognition}; lexer, recovery search, and semantic actions excluded"]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @rbs (Hash[String, ValidationSupport::json_value] table, String values_key) -> Integer
|
|
221
|
+
def occupied(table, values_key)
|
|
222
|
+
if table.key?("rows")
|
|
223
|
+
rows = table.fetch("rows") #: Array[Array[ValidationSupport::json_value]]
|
|
224
|
+
return rows.sum(&:length)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
values = table.fetch(values_key) #: Array[ValidationSupport::json_value]
|
|
228
|
+
values.compact.length
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @rbs (Array[Hash[String, ValidationSupport::json_value]] symbols, String kind) -> Set[Integer]
|
|
232
|
+
def symbol_ids(symbols, kind)
|
|
233
|
+
symbols.filter_map do |symbol|
|
|
234
|
+
next unless symbol.fetch("kind") == kind
|
|
235
|
+
|
|
236
|
+
symbol.fetch("id") #: Integer
|
|
237
|
+
end.to_set
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @rbs (Hash[String, ValidationSupport::json_value] identity,
|
|
241
|
+
# Hash[String, ValidationSupport::json_value] payload, String identity_name,
|
|
242
|
+
# String payload_name) -> void
|
|
243
|
+
def validate_named_digest(identity, payload, identity_name, payload_name)
|
|
244
|
+
expected = Serializer.digest(payload.fetch(payload_name))
|
|
245
|
+
path = "$.identity.#{identity_name}_digest"
|
|
246
|
+
return if identity.fetch("#{identity_name}_digest") == expected
|
|
247
|
+
|
|
248
|
+
invalid(path,
|
|
249
|
+
"does not match payload #{payload_name}")
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
require "set"
|
|
6
|
+
require_relative "error"
|
|
7
|
+
require_relative "ir"
|
|
8
|
+
require_relative "tables"
|
|
9
|
+
require_relative "runtime/table_format"
|
|
10
|
+
require_relative "codegen/cst_metadata"
|
|
11
|
+
require_relative "table_artifact/serializer"
|
|
12
|
+
require_relative "table_artifact/validator"
|
|
13
|
+
require_relative "table_artifact/document"
|
|
14
|
+
require_relative "table_artifact/cst_projection"
|
|
15
|
+
require_relative "table_artifact/builder"
|
|
16
|
+
require_relative "table_artifact/executor"
|
|
17
|
+
|
|
18
|
+
module Ibex
|
|
19
|
+
# Experimental data-only authority for executable parser tables.
|
|
20
|
+
module TableArtifact
|
|
21
|
+
# @rbs!
|
|
22
|
+
# interface _Reader
|
|
23
|
+
# def read: (Integer length) -> String?
|
|
24
|
+
# end
|
|
25
|
+
|
|
26
|
+
ARTIFACT_TYPE = "ibex.parser-table" #: String
|
|
27
|
+
SCHEMA_VERSION = 1 #: Integer
|
|
28
|
+
DEFAULT_MAX_BYTES = 16 * 1024 * 1024 #: Integer
|
|
29
|
+
|
|
30
|
+
class ValidationError < Ibex::Error; end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# @rbs (IR::Automaton automaton, ?representation: Symbol | String, ?cst_trivia: Symbol | String?,
|
|
34
|
+
# ?omit_action_call: bool?) -> Document
|
|
35
|
+
def build(automaton, representation: :compact, cst_trivia: nil, omit_action_call: nil)
|
|
36
|
+
Builder.new(
|
|
37
|
+
automaton,
|
|
38
|
+
representation: representation,
|
|
39
|
+
cst_trivia: cst_trivia,
|
|
40
|
+
omit_action_call: omit_action_call
|
|
41
|
+
).build
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @rbs (String | _Reader source, ?max_bytes: Integer) -> Document
|
|
45
|
+
def load(source, max_bytes: DEFAULT_MAX_BYTES)
|
|
46
|
+
bytes = read_bounded(source, max_bytes)
|
|
47
|
+
unless bytes.dup.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
48
|
+
raise ValidationError, "table artifact must be UTF-8 JSON"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Document.new(JSON.parse(bytes))
|
|
52
|
+
rescue JSON::ParserError => e
|
|
53
|
+
raise ValidationError, "invalid table artifact JSON: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @rbs (untyped source, Integer max_bytes) -> String
|
|
59
|
+
def read_bounded(source, max_bytes)
|
|
60
|
+
raise ArgumentError, "max_bytes must be positive" unless max_bytes.is_a?(Integer) && max_bytes.positive?
|
|
61
|
+
|
|
62
|
+
bytes = if source.respond_to?(:read)
|
|
63
|
+
read_from(source, max_bytes)
|
|
64
|
+
else
|
|
65
|
+
String(source)
|
|
66
|
+
end
|
|
67
|
+
raise ValidationError, "table artifact exceeds #{max_bytes} bytes" if bytes.bytesize > max_bytes
|
|
68
|
+
|
|
69
|
+
bytes
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs (_Reader source, Integer max_bytes) -> String
|
|
73
|
+
def read_from(source, max_bytes)
|
|
74
|
+
bytes = +"".b
|
|
75
|
+
loop do
|
|
76
|
+
chunk = source.read((max_bytes + 1) - bytes.bytesize)
|
|
77
|
+
break if chunk.nil?
|
|
78
|
+
raise ValidationError, "table artifact reader must return strings" unless chunk.is_a?(String)
|
|
79
|
+
break if chunk.empty?
|
|
80
|
+
|
|
81
|
+
bytes << chunk
|
|
82
|
+
break if bytes.bytesize > max_bytes
|
|
83
|
+
end
|
|
84
|
+
bytes
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -7,6 +7,10 @@ module Ibex
|
|
|
7
7
|
module TableSimulation
|
|
8
8
|
# Final immutable simulation document.
|
|
9
9
|
class Result
|
|
10
|
+
# @rbs!
|
|
11
|
+
# type step_document = Hash[String, Step::document_value]
|
|
12
|
+
# type document_value = String | Integer | Array[String] | Array[step_document]
|
|
13
|
+
|
|
10
14
|
IDENTIFIER = "table-simulation" #: String
|
|
11
15
|
SCHEMA_VERSION = 1 #: Integer
|
|
12
16
|
|
|
@@ -29,7 +33,7 @@ module Ibex
|
|
|
29
33
|
freeze
|
|
30
34
|
end
|
|
31
35
|
|
|
32
|
-
# @rbs () -> Hash[String,
|
|
36
|
+
# @rbs () -> Hash[String, document_value]
|
|
33
37
|
def to_h
|
|
34
38
|
{
|
|
35
39
|
"ibex_table_simulation" => IDENTIFIER,
|
|
@@ -42,7 +46,7 @@ module Ibex
|
|
|
42
46
|
}
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
# @rbs (*
|
|
49
|
+
# @rbs (*Object?) -> String
|
|
46
50
|
def to_json(*)
|
|
47
51
|
"#{JSON.pretty_generate(to_h)}\n"
|
|
48
52
|
end
|
|
@@ -5,6 +5,8 @@ module Ibex
|
|
|
5
5
|
module TableSimulation
|
|
6
6
|
# One immutable parser-table action.
|
|
7
7
|
class Step
|
|
8
|
+
# @rbs! type document_value = String | Integer | nil
|
|
9
|
+
|
|
8
10
|
attr_reader :sequence #: Integer
|
|
9
11
|
attr_reader :state #: Integer
|
|
10
12
|
attr_reader :token_id #: Integer
|
|
@@ -38,7 +40,7 @@ module Ibex
|
|
|
38
40
|
freeze
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
# @rbs () -> Hash[String,
|
|
43
|
+
# @rbs () -> Hash[String, document_value]
|
|
42
44
|
def to_h
|
|
43
45
|
{
|
|
44
46
|
"sequence" => @sequence,
|
data/lib/ibex/tables.rb
CHANGED
|
@@ -8,11 +8,25 @@ module Ibex
|
|
|
8
8
|
# Parser table construction and row-displacement compression.
|
|
9
9
|
module Tables
|
|
10
10
|
# @rbs!
|
|
11
|
+
# type action_table = Array[Hash[Integer, IR::runtime_action]] | CompactActions
|
|
12
|
+
# type goto_table = Array[Hash[Integer, Integer]] | Compact
|
|
11
13
|
# private def runtime_action: (IR::parser_action action) -> IR::runtime_action
|
|
12
14
|
# private def self.runtime_action: (IR::parser_action action) -> IR::runtime_action
|
|
13
15
|
|
|
16
|
+
# @rbs skip
|
|
14
17
|
TableSet = Struct.new(:actions, :gotos, :default_actions, keyword_init: true)
|
|
15
18
|
|
|
19
|
+
# @rbs!
|
|
20
|
+
# class TableSet < Struct[Object?]
|
|
21
|
+
# attr_accessor actions: action_table
|
|
22
|
+
# attr_accessor gotos: goto_table
|
|
23
|
+
# attr_accessor default_actions: Array[IR::runtime_action?]
|
|
24
|
+
# def self.new: (?actions: action_table, ?gotos: goto_table,
|
|
25
|
+
# ?default_actions: Array[IR::runtime_action?]) -> instance
|
|
26
|
+
# | ({ ?actions: action_table, ?gotos: goto_table,
|
|
27
|
+
# ?default_actions: Array[IR::runtime_action?] }) -> instance
|
|
28
|
+
# end
|
|
29
|
+
|
|
16
30
|
# @rbs (IR::Automaton automaton, ?format: Symbol | String) -> TableSet
|
|
17
31
|
def build(automaton, format: :compact)
|
|
18
32
|
action_rows = automaton.states.map do |state|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "artifact_set"
|
|
5
|
+
require_relative "generation_manifest"
|
|
6
|
+
require_relative "generation_transaction"
|
|
7
|
+
require_relative "table_artifact"
|
|
8
|
+
require_relative "verification_report"
|
|
9
|
+
|
|
10
|
+
module Ibex
|
|
11
|
+
# Explicitly renders and publishes one manifest-bound verification bundle.
|
|
12
|
+
class VerifiableGenerationBundle
|
|
13
|
+
# rubocop:disable Metrics/ParameterLists -- persisted artifact paths and verification bounds are explicit inputs.
|
|
14
|
+
# @rbs (IR::Automaton automaton, wrapper_path: String, wrapper_source: String,
|
|
15
|
+
# table_path: String, report_path: String, manifest_path: String,
|
|
16
|
+
# source_records: Array[GenerationInput], ?manifest_options: Hash[String, Object?],
|
|
17
|
+
# ?wrapper_mode: Integer?, ?representation: Symbol | String, ?cst_trivia: Symbol | String?,
|
|
18
|
+
# ?omit_action_call: bool?, ?strict: bool, ?max_states: Integer, ?max_items: Integer) -> void
|
|
19
|
+
def initialize(automaton, wrapper_path:, wrapper_source:, table_path:, report_path:, manifest_path:,
|
|
20
|
+
source_records:, manifest_options: {}, wrapper_mode: nil, representation: :compact,
|
|
21
|
+
cst_trivia: nil, omit_action_call: nil, strict: false,
|
|
22
|
+
max_states: VerificationReport::DEFAULT_MAX_STATES,
|
|
23
|
+
max_items: VerificationReport::DEFAULT_MAX_ITEMS)
|
|
24
|
+
@automaton = automaton
|
|
25
|
+
@wrapper_path = wrapper_path
|
|
26
|
+
@wrapper_source = wrapper_source
|
|
27
|
+
@table_path = table_path
|
|
28
|
+
VerificationReport::LogicalPath.table(table_path)
|
|
29
|
+
if source_records.length > VerificationReport::LogicalPath::MAX_INPUT_FILES
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"verification reports support at most #{VerificationReport::LogicalPath::MAX_INPUT_FILES} input files"
|
|
32
|
+
end
|
|
33
|
+
@report_path = report_path
|
|
34
|
+
@manifest_path = manifest_path
|
|
35
|
+
@source_records = source_records
|
|
36
|
+
@manifest_options = manifest_options
|
|
37
|
+
@wrapper_mode = wrapper_mode
|
|
38
|
+
@representation = representation
|
|
39
|
+
@cst_trivia = cst_trivia
|
|
40
|
+
@omit_action_call = omit_action_call
|
|
41
|
+
@strict = strict
|
|
42
|
+
@max_states = max_states
|
|
43
|
+
@max_items = max_items
|
|
44
|
+
end
|
|
45
|
+
# rubocop:enable Metrics/ParameterLists
|
|
46
|
+
|
|
47
|
+
# Render table, wrapper, and report before the non-cyclic manifest marker.
|
|
48
|
+
# @rbs () -> ArtifactSet
|
|
49
|
+
def render
|
|
50
|
+
canonical_automaton = VerificationReport.canonical_automaton(
|
|
51
|
+
@automaton, source_records: @source_records
|
|
52
|
+
)
|
|
53
|
+
table = TableArtifact.build(
|
|
54
|
+
canonical_automaton, representation: @representation, cst_trivia: @cst_trivia,
|
|
55
|
+
omit_action_call: @omit_action_call
|
|
56
|
+
)
|
|
57
|
+
artifacts = ArtifactSet.new
|
|
58
|
+
artifacts.add(kind: :parser_table, path: @table_path, content: table.dump)
|
|
59
|
+
artifacts.add(kind: :parser, path: @wrapper_path, content: @wrapper_source, mode: @wrapper_mode)
|
|
60
|
+
report = VerificationReport.render(
|
|
61
|
+
canonical_automaton, table: table, source_records: @source_records, table_path: @table_path,
|
|
62
|
+
strict: @strict, max_states: @max_states, max_items: @max_items
|
|
63
|
+
)
|
|
64
|
+
artifacts.add(kind: :verification_report, path: @report_path, content: report)
|
|
65
|
+
manifest = GenerationManifest.render(
|
|
66
|
+
artifacts, source_records: @source_records, options: @manifest_options
|
|
67
|
+
)
|
|
68
|
+
artifacts.add(kind: :manifest, path: @manifest_path, content: manifest)
|
|
69
|
+
artifacts
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs (?warning: ^(String) -> void, ?stability_check: ^() -> bool) -> ArtifactSet
|
|
73
|
+
def publish(warning: ->(_message) {}, stability_check: -> { @source_records.all?(&:current?) })
|
|
74
|
+
artifacts = render
|
|
75
|
+
GenerationTransaction.new(
|
|
76
|
+
artifacts, warning: warning, stability_check: stability_check, source_records: @source_records
|
|
77
|
+
).commit
|
|
78
|
+
artifacts
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs (String manifest_path) -> Hash[String, VerificationReport::Validator::json_value]
|
|
82
|
+
def self.validate_file(manifest_path)
|
|
83
|
+
VerificationReport.validate_bundle_file(manifest_path)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require_relative "../ir"
|
|
6
|
+
require_relative "../verify"
|
|
7
|
+
require_relative "../version"
|
|
8
|
+
|
|
9
|
+
module Ibex
|
|
10
|
+
module VerificationReport
|
|
11
|
+
# Builds deterministic evidence from an Automaton IR verification run.
|
|
12
|
+
class Builder
|
|
13
|
+
# @rbs!
|
|
14
|
+
# type json_value = String | Integer | Float | bool | nil | Array[json_value] | Hash[String, json_value]
|
|
15
|
+
|
|
16
|
+
# @rbs (IR::Automaton automaton, table: TableArtifact::Document,
|
|
17
|
+
# source_records: Array[GenerationInput], table_path: String, strict: bool,
|
|
18
|
+
# max_states: Integer, max_items: Integer) -> void
|
|
19
|
+
def initialize(automaton, table:, source_records:, table_path:, strict:, max_states:, max_items:)
|
|
20
|
+
raise ArgumentError, "source_records must not be empty" if source_records.empty?
|
|
21
|
+
if source_records.length > LogicalPath::MAX_INPUT_FILES
|
|
22
|
+
raise ArgumentError, "verification reports support at most #{LogicalPath::MAX_INPUT_FILES} input files"
|
|
23
|
+
end
|
|
24
|
+
raise ArgumentError, "max_states must be positive" unless max_states.is_a?(Integer) && max_states.positive?
|
|
25
|
+
raise ArgumentError, "max_items must be positive" unless max_items.is_a?(Integer) && max_items.positive?
|
|
26
|
+
|
|
27
|
+
@automaton = CanonicalIR.new(automaton, source_records: source_records).build
|
|
28
|
+
@table = table
|
|
29
|
+
@source_records = source_records
|
|
30
|
+
@table_path = LogicalPath.table(table_path)
|
|
31
|
+
@strict = strict
|
|
32
|
+
@max_states = max_states
|
|
33
|
+
@max_items = max_items
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @rbs () -> String
|
|
37
|
+
def render
|
|
38
|
+
validate_table_identity!
|
|
39
|
+
document = {
|
|
40
|
+
"ibex_report" => IDENTIFIER,
|
|
41
|
+
"schema_version" => SCHEMA_VERSION,
|
|
42
|
+
"checker" => { "name" => "ibex.verify", "version" => Ibex::VERSION },
|
|
43
|
+
"profile" => @strict ? "strict" : "default",
|
|
44
|
+
"bounds" => bounds.transform_keys(&:to_s),
|
|
45
|
+
"input" => input_identity,
|
|
46
|
+
"ir" => ir_identity,
|
|
47
|
+
"table" => table_identity,
|
|
48
|
+
"outcome" => verification_outcome,
|
|
49
|
+
"excluded_trust" => EXCLUDED_TRUST
|
|
50
|
+
}
|
|
51
|
+
document["evidence_digest"] = TableArtifact::Serializer.digest(document)
|
|
52
|
+
TableArtifact::Serializer.dump(document)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# @rbs () -> Hash[Symbol, Integer]
|
|
58
|
+
def bounds
|
|
59
|
+
{ max_states: @max_states, max_items: @max_items }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @rbs () -> Array[String]
|
|
63
|
+
def requested_checks
|
|
64
|
+
Verify::Verifier::DEFAULT_CHECKS + (@strict ? Verify::Verifier::STRICT_CHECKS : [])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs () -> Hash[String, json_value]
|
|
68
|
+
def verification_outcome
|
|
69
|
+
result = Verify::Verifier.new(
|
|
70
|
+
@automaton, strict: @strict, max_states: @max_states, max_items: @max_items
|
|
71
|
+
).verify
|
|
72
|
+
{
|
|
73
|
+
"status" => result.valid? ? "pass" : "violations",
|
|
74
|
+
"requested_checks" => requested_checks,
|
|
75
|
+
"executed_checks" => result.checks,
|
|
76
|
+
"violations" => result.violations.map { |violation| stringify_keys(violation.to_h) },
|
|
77
|
+
"exhaustion" => nil
|
|
78
|
+
}
|
|
79
|
+
rescue Verify::BudgetExceeded => e
|
|
80
|
+
{
|
|
81
|
+
"status" => "exhausted",
|
|
82
|
+
"requested_checks" => requested_checks,
|
|
83
|
+
"executed_checks" => [],
|
|
84
|
+
"violations" => [],
|
|
85
|
+
"exhaustion" => { "kind" => "reference_collection_budget", "message" => e.message }
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @rbs () -> Hash[String, json_value]
|
|
90
|
+
def input_identity
|
|
91
|
+
files = @source_records.map.with_index do |record, index|
|
|
92
|
+
{
|
|
93
|
+
"logical_path" => LogicalPath.input(record.path, index),
|
|
94
|
+
"sha256" => prefixed_digest(record.sha256),
|
|
95
|
+
"bytesize" => record.bytesize
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
{ "digest" => TableArtifact::Serializer.digest(files), "files" => files }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs () -> Hash[String, json_value]
|
|
102
|
+
def ir_identity
|
|
103
|
+
{
|
|
104
|
+
"identity_scope" => IR_IDENTITY_SCOPE,
|
|
105
|
+
"grammar" => {
|
|
106
|
+
"schema_version" => @automaton.grammar.schema_version,
|
|
107
|
+
"digest" => grammar_digest
|
|
108
|
+
},
|
|
109
|
+
"automaton" => {
|
|
110
|
+
"schema_version" => @automaton.schema_version,
|
|
111
|
+
"algorithm" => @automaton.algorithm,
|
|
112
|
+
"digest" => automaton_digest
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @rbs () -> Hash[String, json_value]
|
|
118
|
+
def table_identity
|
|
119
|
+
{
|
|
120
|
+
"logical_path" => @table_path,
|
|
121
|
+
"artifact_type" => TableArtifact::ARTIFACT_TYPE,
|
|
122
|
+
"schema_version" => TableArtifact::SCHEMA_VERSION,
|
|
123
|
+
"representation" => @table.payload.dig("table_format", "representation"),
|
|
124
|
+
"artifact_digest" => prefixed_digest(Digest::SHA256.hexdigest(@table.dump)),
|
|
125
|
+
"payload_digest" => @table.identity.fetch("payload_digest")
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs () -> void
|
|
130
|
+
def validate_table_identity!
|
|
131
|
+
identity = @table.identity
|
|
132
|
+
return if identity.fetch("grammar_digest") == grammar_digest &&
|
|
133
|
+
identity.fetch("automaton_digest") == automaton_digest
|
|
134
|
+
|
|
135
|
+
raise ArgumentError, "table artifact does not match the supplied Automaton IR"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs () -> String
|
|
139
|
+
def grammar_digest
|
|
140
|
+
@grammar_digest ||= "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(@automaton.grammar))}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs () -> String
|
|
144
|
+
def automaton_digest
|
|
145
|
+
@automaton_digest ||= "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(@automaton))}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs (String digest) -> String
|
|
149
|
+
def prefixed_digest(digest)
|
|
150
|
+
digest.start_with?("sha256:") ? digest : "sha256:#{digest}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs (Hash[Symbol, String] value) -> Hash[String, String]
|
|
154
|
+
def stringify_keys(value)
|
|
155
|
+
value.to_h { |key, child| [key.to_s, child] }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|