ibex 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +247 -104
- data/docs/architecture.md +322 -28
- data/docs/cst-migration.md +107 -0
- data/docs/cst.md +213 -0
- data/docs/development.md +129 -0
- data/docs/editor-setup.md +25 -0
- data/docs/error-ux.md +60 -0
- data/docs/grammar-reference.md +458 -18
- data/docs/lexer-migration.md +51 -0
- data/docs/racc-migration.md +34 -6
- data/docs/release-readiness.md +163 -0
- data/docs/stability.md +124 -0
- data/examples/README.md +60 -0
- data/examples/calculator.y +44 -0
- data/examples/csv.y +32 -0
- data/examples/ini.y +70 -0
- data/examples/json.y +48 -0
- data/examples/tiny_language.y +75 -0
- data/lib/ibex/analysis/sets.rb +5 -4
- data/lib/ibex/artifact_set.rb +63 -0
- data/lib/ibex/cli/ambiguity.rb +66 -0
- data/lib/ibex/cli/counterexample_options.rb +6 -2
- data/lib/ibex/cli/coverage.rb +173 -0
- data/lib/ibex/cli/debug.rb +106 -0
- data/lib/ibex/cli/diagnostics.rb +116 -0
- data/lib/ibex/cli/documentation.rb +67 -0
- data/lib/ibex/cli/error_messages.rb +177 -0
- data/lib/ibex/cli/explain.rb +76 -0
- data/lib/ibex/cli/formatting.rb +386 -0
- data/lib/ibex/cli/generation_artifacts.rb +138 -0
- data/lib/ibex/cli/generation_error_messages.rb +39 -0
- data/lib/ibex/cli/grammar_tests.rb +122 -0
- data/lib/ibex/cli/ir_tools.rb +184 -0
- data/lib/ibex/cli/lsp.rb +33 -0
- data/lib/ibex/cli/outputs.rb +91 -8
- data/lib/ibex/cli/racc_migration.rb +87 -0
- data/lib/ibex/cli/samples.rb +92 -0
- data/lib/ibex/cli/watch.rb +103 -0
- data/lib/ibex/cli.rb +506 -37
- data/lib/ibex/codegen/action_locations.rb +103 -0
- data/lib/ibex/codegen/action_method_source.rb +210 -0
- data/lib/ibex/codegen/action_source.rb +118 -0
- data/lib/ibex/codegen/ambiguity.rb +173 -0
- data/lib/ibex/codegen/cst_metadata.rb +171 -0
- data/lib/ibex/codegen/documentation.rb +170 -0
- data/lib/ibex/codegen/explain.rb +312 -0
- data/lib/ibex/codegen/generated_action_abi.rb +288 -0
- data/lib/ibex/codegen/html.rb +94 -10
- data/lib/ibex/codegen/mermaid.rb +43 -0
- data/lib/ibex/codegen/railroad.rb +197 -0
- data/lib/ibex/codegen/railroad_documentation.rb +59 -0
- data/lib/ibex/codegen/rbs.rb +323 -2
- data/lib/ibex/codegen/report.rb +46 -6
- data/lib/ibex/codegen/ruby.rb +334 -59
- data/lib/ibex/codegen/ruby_actions.rb +143 -0
- data/lib/ibex/codegen/ruby_ast.rb +121 -0
- data/lib/ibex/codegen/ruby_error_messages.rb +28 -0
- data/lib/ibex/codegen/ruby_lexer.rb +83 -0
- data/lib/ibex/codegen/ruby_syntax.rb +90 -0
- data/lib/ibex/codegen/ruby_table_metadata.rb +57 -0
- data/lib/ibex/codegen/ruby_value_printers.rb +56 -0
- data/lib/ibex/codegen/symbol_labels.rb +1 -1
- data/lib/ibex/coverage/collector.rb +188 -0
- data/lib/ibex/coverage/event_stream.rb +97 -0
- data/lib/ibex/coverage/report.rb +259 -0
- data/lib/ibex/coverage/runtime_event_validator.rb +160 -0
- data/lib/ibex/coverage.rb +13 -0
- data/lib/ibex/error_messages/parser.rb +159 -0
- data/lib/ibex/error_messages/parser_v2.rb +198 -0
- data/lib/ibex/error_messages/renderer.rb +65 -0
- data/lib/ibex/error_messages/sentence_search.rb +196 -0
- data/lib/ibex/error_messages/update.rb +169 -0
- data/lib/ibex/error_messages.rb +165 -0
- data/lib/ibex/frontend/ast.rb +145 -6
- data/lib/ibex/frontend/bootstrap_parser.rb +47 -4
- data/lib/ibex/frontend/diagnostic.rb +81 -0
- data/lib/ibex/frontend/diagnostic_recovery.rb +268 -0
- data/lib/ibex/frontend/dsl.rb +66 -7
- data/lib/ibex/frontend/formatter.rb +407 -0
- data/lib/ibex/frontend/generated_parser.rb +614 -190
- data/lib/ibex/frontend/generated_parser_base.rb +164 -30
- data/lib/ibex/frontend/generated_parser_includes.rb +61 -0
- data/lib/ibex/frontend/generated_parser_metadata.rb +61 -0
- data/lib/ibex/frontend/generated_parser_parameters.rb +60 -0
- data/lib/ibex/frontend/generation.rb +33 -0
- data/lib/ibex/frontend/lexer.rb +162 -10
- data/lib/ibex/frontend/lexer_recovery.rb +84 -0
- data/lib/ibex/frontend/parser/declarations.rb +247 -11
- data/lib/ibex/frontend/parser/parameters.rb +82 -0
- data/lib/ibex/frontend/parser/rules.rb +43 -6
- data/lib/ibex/frontend/parser.rb +182 -4
- data/lib/ibex/frontend/regenerator.rb +26 -1
- data/lib/ibex/frontend/resolution.rb +69 -0
- data/lib/ibex/frontend/resolver.rb +217 -0
- data/lib/ibex/frontend/rule_documentation.rb +103 -0
- data/lib/ibex/frontend/source_cursor.rb +132 -8
- data/lib/ibex/frontend/source_document.rb +229 -0
- data/lib/ibex/frontend/source_loader.rb +150 -0
- data/lib/ibex/frontend/source_span.rb +81 -0
- data/lib/ibex/frontend/token_adapter/declaration_document_state.rb +47 -0
- data/lib/ibex/frontend/token_adapter/declaration_lexer_state.rb +83 -0
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +216 -26
- data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +8 -2
- data/lib/ibex/frontend/token_adapter/rule_state.rb +60 -2
- data/lib/ibex/frontend/token_adapter.rb +8 -3
- data/lib/ibex/frontend.rb +13 -2
- data/lib/ibex/generation_input.rb +57 -0
- data/lib/ibex/generation_manifest.rb +200 -0
- data/lib/ibex/generation_transaction.rb +261 -0
- data/lib/ibex/generation_transaction_recovery.rb +109 -0
- data/lib/ibex/generation_transaction_validation.rb +196 -0
- data/lib/ibex/grammar_tests.rb +206 -0
- data/lib/ibex/ir/automaton_ir.rb +38 -5
- data/lib/ibex/ir/grammar_ir.rb +137 -24
- data/lib/ibex/ir/lexer_ir.rb +76 -0
- data/lib/ibex/ir/migration.rb +120 -0
- data/lib/ibex/ir/serialize.rb +110 -19
- data/lib/ibex/ir/validator/automaton.rb +345 -0
- data/lib/ibex/ir/validator/base.rb +129 -0
- data/lib/ibex/ir/validator/grammar.rb +604 -0
- data/lib/ibex/ir/validator/lexer.rb +113 -0
- data/lib/ibex/ir/validator.rb +62 -0
- data/lib/ibex/ir.rb +57 -4
- data/lib/ibex/lalr/build_metrics.rb +23 -0
- data/lib/ibex/lalr/builder.rb +379 -52
- data/lib/ibex/lalr/conflict.rb +1 -0
- data/lib/ibex/lalr/conflict_search.rb +11 -5
- data/lib/ibex/lalr/counterexample.rb +20 -5
- data/lib/ibex/lalr/direct_lookaheads.rb +236 -0
- data/lib/ibex/lalr/ielr_partition.rb +152 -0
- data/lib/ibex/lalr/on_error_reductions.rb +74 -0
- data/lib/ibex/lalr.rb +7 -0
- data/lib/ibex/location.rb +129 -0
- data/lib/ibex/lsp/document_handlers.rb +66 -0
- data/lib/ibex/lsp/document_store.rb +264 -0
- data/lib/ibex/lsp/document_store_diagnostics.rb +44 -0
- data/lib/ibex/lsp/document_store_validation.rb +61 -0
- data/lib/ibex/lsp/initialization_handlers.rb +78 -0
- data/lib/ibex/lsp/navigation_handlers.rb +56 -0
- data/lib/ibex/lsp/position_codec.rb +109 -0
- data/lib/ibex/lsp/protocol_error.rb +33 -0
- data/lib/ibex/lsp/request_handlers.rb +45 -0
- data/lib/ibex/lsp/request_support.rb +87 -0
- data/lib/ibex/lsp/server.rb +146 -0
- data/lib/ibex/lsp/symbol_index.rb +241 -0
- data/lib/ibex/lsp/symbol_index_builder.rb +267 -0
- data/lib/ibex/lsp/symbol_index_precedence_references.rb +44 -0
- data/lib/ibex/lsp/symbol_index_source_queries.rb +61 -0
- data/lib/ibex/lsp/symbol_occurrence.rb +17 -0
- data/lib/ibex/lsp/transport.rb +118 -0
- data/lib/ibex/lsp/workspace.rb +127 -0
- data/lib/ibex/lsp/workspace_analyzer.rb +199 -0
- data/lib/ibex/lsp.rb +32 -0
- data/lib/ibex/normalize/declarations.rb +140 -7
- data/lib/ibex/normalize/diagnostics.rb +50 -5
- data/lib/ibex/normalize/expander.rb +59 -55
- data/lib/ibex/normalize/expression.rb +60 -39
- data/lib/ibex/normalize/inline_expansion.rb +414 -0
- data/lib/ibex/normalize/inline_validation.rb +174 -0
- data/lib/ibex/normalize/lexer.rb +131 -0
- data/lib/ibex/normalize/named_references.rb +60 -0
- data/lib/ibex/normalize/nodes.rb +46 -0
- data/lib/ibex/normalize/parameter_ebnf_lowering.rb +69 -0
- data/lib/ibex/normalize/parameter_lowering.rb +126 -0
- data/lib/ibex/normalize/parameter_substitution.rb +125 -0
- data/lib/ibex/normalize/parameter_validation.rb +140 -0
- data/lib/ibex/normalize/parameters.rb +199 -0
- data/lib/ibex/normalize/recovery_declarations.rb +84 -0
- data/lib/ibex/normalize.rb +179 -14
- data/lib/ibex/racc_migration/checker.rb +122 -0
- data/lib/ibex/racc_migration/harness.rb +177 -0
- data/lib/ibex/racc_migration/report.rb +94 -0
- data/lib/ibex/racc_migration.rb +12 -0
- data/lib/ibex/rake_task.rb +116 -0
- data/lib/ibex/samples.rb +186 -0
- data/lib/ibex/table_simulation/result.rb +51 -0
- data/lib/ibex/table_simulation/simulator.rb +253 -0
- data/lib/ibex/table_simulation/step.rb +60 -0
- data/lib/ibex/table_simulation/text.rb +31 -0
- data/lib/ibex/table_simulation.rb +13 -0
- data/lib/ibex/tables.rb +9 -70
- data/lib/ibex/version.rb +1 -1
- data/lib/ibex/watch/runner.rb +172 -0
- data/lib/ibex/watch/source_snapshot.rb +93 -0
- data/lib/ibex/watch.rb +11 -0
- data/lib/ibex.rb +25 -1
- data/schema/automaton-ir-v1.schema.json +401 -0
- data/schema/automaton-ir-v2.schema.json +58 -0
- data/schema/benchmark-v1.schema.json +212 -0
- data/schema/benchmark-v2.schema.json +61 -0
- data/schema/cst-v1.json +170 -0
- data/schema/error-ux-v1.schema.json +258 -0
- data/schema/explain-v1.schema.json +433 -0
- data/schema/frontend-diagnostics-v1.schema.json +154 -0
- data/schema/generation-manifest-v1.schema.json +115 -0
- data/schema/grammar-ir-v1.schema.json +426 -0
- data/schema/grammar-ir-v2.schema.json +779 -0
- data/schema/lexer-ir-v1.schema.json +215 -0
- data/schema/migration-check-v1.schema.json +60 -0
- data/schema/performance-comparison-v1.schema.json +395 -0
- data/schema/public-performance-comparison-v1.schema.json +506 -0
- data/schema/public-performance-profile-v1.schema.json +360 -0
- data/schema/runtime-coverage-v1.schema.json +86 -0
- data/schema/runtime-event-v1.schema.json +308 -0
- data/schema/table-simulation-v1.schema.json +70 -0
- data/sig/ibex/artifact_set.rbs +37 -0
- data/sig/ibex/cli/ambiguity.rbs +22 -0
- data/sig/ibex/cli/counterexample_options.rbs +2 -0
- data/sig/ibex/cli/coverage.rbs +53 -0
- data/sig/ibex/cli/debug.rbs +28 -0
- data/sig/ibex/cli/diagnostics.rbs +38 -0
- data/sig/ibex/cli/documentation.rbs +25 -0
- data/sig/ibex/cli/error_messages.rbs +57 -0
- data/sig/ibex/cli/explain.rbs +25 -0
- data/sig/ibex/cli/formatting.rbs +103 -0
- data/sig/ibex/cli/generation_artifacts.rbs +50 -0
- data/sig/ibex/cli/generation_error_messages.rbs +19 -0
- data/sig/ibex/cli/grammar_tests.rbs +36 -0
- data/sig/ibex/cli/ir_tools.rbs +51 -0
- data/sig/ibex/cli/lsp.rbs +14 -0
- data/sig/ibex/cli/outputs.rbs +17 -0
- data/sig/ibex/cli/racc_migration.rbs +30 -0
- data/sig/ibex/cli/samples.rbs +30 -0
- data/sig/ibex/cli/watch.rbs +43 -0
- data/sig/ibex/cli.rbs +104 -5
- data/sig/ibex/codegen/action_locations.rbs +45 -0
- data/sig/ibex/codegen/action_method_source.rbs +65 -0
- data/sig/ibex/codegen/action_source.rbs +50 -0
- data/sig/ibex/codegen/ambiguity.rbs +60 -0
- data/sig/ibex/codegen/cst_metadata.rbs +59 -0
- data/sig/ibex/codegen/documentation.rbs +50 -0
- data/sig/ibex/codegen/explain.rbs +85 -0
- data/sig/ibex/codegen/generated_action_abi.rbs +101 -0
- data/sig/ibex/codegen/html.rbs +18 -2
- data/sig/ibex/codegen/mermaid.rbs +16 -0
- data/sig/ibex/codegen/railroad.rbs +82 -0
- data/sig/ibex/codegen/railroad_documentation.rbs +31 -0
- data/sig/ibex/codegen/rbs.rbs +84 -4
- data/sig/ibex/codegen/report.rbs +8 -0
- data/sig/ibex/codegen/ruby.rbs +97 -23
- data/sig/ibex/codegen/ruby_actions.rbs +54 -0
- data/sig/ibex/codegen/ruby_ast.rbs +34 -0
- data/sig/ibex/codegen/ruby_error_messages.rbs +16 -0
- data/sig/ibex/codegen/ruby_lexer.rbs +25 -0
- data/sig/ibex/codegen/ruby_syntax.rbs +25 -0
- data/sig/ibex/codegen/ruby_table_metadata.rbs +26 -0
- data/sig/ibex/codegen/ruby_value_printers.rbs +28 -0
- data/sig/ibex/coverage/collector.rbs +76 -0
- data/sig/ibex/coverage/event_stream.rbs +42 -0
- data/sig/ibex/coverage/report.rbs +100 -0
- data/sig/ibex/coverage/runtime_event_validator.rbs +68 -0
- data/sig/ibex/coverage.rbs +7 -0
- data/sig/ibex/error_messages/parser.rbs +58 -0
- data/sig/ibex/error_messages/parser_v2.rbs +67 -0
- data/sig/ibex/error_messages/renderer.rbs +23 -0
- data/sig/ibex/error_messages/sentence_search.rbs +80 -0
- data/sig/ibex/error_messages/update.rbs +43 -0
- data/sig/ibex/error_messages.rbs +85 -0
- data/sig/ibex/frontend/ast.rbs +208 -19
- data/sig/ibex/frontend/bootstrap_parser.rbs +11 -0
- data/sig/ibex/frontend/diagnostic.rbs +53 -0
- data/sig/ibex/frontend/diagnostic_recovery.rbs +98 -0
- data/sig/ibex/frontend/dsl.rbs +33 -4
- data/sig/ibex/frontend/formatter.rbs +135 -0
- data/sig/ibex/frontend/generated_parser.rbs +218 -68
- data/sig/ibex/frontend/generated_parser_base.rbs +61 -10
- data/sig/ibex/frontend/generated_parser_includes.rbs +23 -0
- data/sig/ibex/frontend/generated_parser_metadata.rbs +23 -0
- data/sig/ibex/frontend/generated_parser_parameters.rbs +24 -0
- data/sig/ibex/frontend/generation.rbs +6 -0
- data/sig/ibex/frontend/lexer.rbs +49 -2
- data/sig/ibex/frontend/lexer_recovery.rbs +25 -0
- data/sig/ibex/frontend/parser/declarations.rbs +54 -0
- data/sig/ibex/frontend/parser/parameters.rbs +28 -0
- data/sig/ibex/frontend/parser/rules.rbs +3 -0
- data/sig/ibex/frontend/parser.rbs +66 -0
- data/sig/ibex/frontend/regenerator.rbs +11 -0
- data/sig/ibex/frontend/resolution.rbs +33 -0
- data/sig/ibex/frontend/resolver.rbs +91 -0
- data/sig/ibex/frontend/rule_documentation.rbs +42 -0
- data/sig/ibex/frontend/source_cursor.rbs +36 -3
- data/sig/ibex/frontend/source_document.rbs +119 -0
- data/sig/ibex/frontend/source_loader.rbs +66 -0
- data/sig/ibex/frontend/source_span.rbs +53 -0
- data/sig/ibex/frontend/token_adapter/declaration_document_state.rbs +21 -0
- data/sig/ibex/frontend/token_adapter/declaration_lexer_state.rbs +27 -0
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +73 -5
- data/sig/ibex/frontend/token_adapter/rule_state.rbs +20 -0
- data/sig/ibex/frontend/token_adapter.rbs +5 -2
- data/sig/ibex/frontend.rbs +1 -1
- data/sig/ibex/generation_input.rbs +37 -0
- data/sig/ibex/generation_manifest.rbs +67 -0
- data/sig/ibex/generation_transaction.rbs +82 -0
- data/sig/ibex/generation_transaction_recovery.rbs +36 -0
- data/sig/ibex/generation_transaction_validation.rbs +65 -0
- data/sig/ibex/grammar_tests.rbs +93 -0
- data/sig/ibex/ir/automaton_ir.rbs +8 -2
- data/sig/ibex/ir/grammar_ir.rbs +75 -15
- data/sig/ibex/ir/lexer_ir.rbs +57 -0
- data/sig/ibex/ir/migration.rbs +34 -0
- data/sig/ibex/ir/serialize.rbs +23 -6
- data/sig/ibex/ir/validator/automaton.rbs +109 -0
- data/sig/ibex/ir/validator/base.rbs +65 -0
- data/sig/ibex/ir/validator/grammar.rbs +184 -0
- data/sig/ibex/ir/validator/lexer.rbs +37 -0
- data/sig/ibex/ir/validator.rbs +16 -0
- data/sig/ibex/ir.rbs +38 -4
- data/sig/ibex/lalr/build_metrics.rbs +20 -0
- data/sig/ibex/lalr/builder.rbs +95 -15
- data/sig/ibex/lalr/conflict_search.rbs +7 -3
- data/sig/ibex/lalr/counterexample.rbs +5 -2
- data/sig/ibex/lalr/direct_lookaheads.rbs +86 -0
- data/sig/ibex/lalr/ielr_partition.rbs +59 -0
- data/sig/ibex/lalr/on_error_reductions.rbs +22 -0
- data/sig/ibex/lalr.rbs +6 -0
- data/sig/ibex/location.rbs +67 -0
- data/sig/ibex/lsp/document_handlers.rbs +24 -0
- data/sig/ibex/lsp/document_store.rbs +94 -0
- data/sig/ibex/lsp/document_store_diagnostics.rbs +20 -0
- data/sig/ibex/lsp/document_store_validation.rbs +26 -0
- data/sig/ibex/lsp/initialization_handlers.rbs +30 -0
- data/sig/ibex/lsp/navigation_handlers.rbs +30 -0
- data/sig/ibex/lsp/position_codec.rbs +40 -0
- data/sig/ibex/lsp/protocol_error.rbs +32 -0
- data/sig/ibex/lsp/request_handlers.rbs +26 -0
- data/sig/ibex/lsp/request_support.rbs +40 -0
- data/sig/ibex/lsp/server.rbs +55 -0
- data/sig/ibex/lsp/symbol_index.rbs +78 -0
- data/sig/ibex/lsp/symbol_index_builder.rbs +87 -0
- data/sig/ibex/lsp/symbol_index_precedence_references.rbs +18 -0
- data/sig/ibex/lsp/symbol_index_source_queries.rbs +26 -0
- data/sig/ibex/lsp/symbol_occurrence.rbs +25 -0
- data/sig/ibex/lsp/transport.rbs +35 -0
- data/sig/ibex/lsp/workspace.rbs +41 -0
- data/sig/ibex/lsp/workspace_analyzer.rbs +69 -0
- data/sig/ibex/lsp.rbs +7 -0
- data/sig/ibex/normalize/declarations.rbs +31 -0
- data/sig/ibex/normalize/diagnostics.rbs +9 -0
- data/sig/ibex/normalize/expander.rbs +20 -14
- data/sig/ibex/normalize/expression.rbs +10 -10
- data/sig/ibex/normalize/inline_expansion.rbs +120 -0
- data/sig/ibex/normalize/inline_validation.rbs +40 -0
- data/sig/ibex/normalize/lexer.rbs +37 -0
- data/sig/ibex/normalize/named_references.rbs +20 -0
- data/sig/ibex/normalize/nodes.rbs +14 -0
- data/sig/ibex/normalize/parameter_ebnf_lowering.rbs +32 -0
- data/sig/ibex/normalize/parameter_lowering.rbs +35 -0
- data/sig/ibex/normalize/parameter_substitution.rbs +42 -0
- data/sig/ibex/normalize/parameter_validation.rbs +44 -0
- data/sig/ibex/normalize/parameters.rbs +46 -0
- data/sig/ibex/normalize/recovery_declarations.rbs +24 -0
- data/sig/ibex/normalize.rbs +123 -18
- data/sig/ibex/racc_migration/checker.rbs +36 -0
- data/sig/ibex/racc_migration/harness.rbs +16 -0
- data/sig/ibex/racc_migration/report.rbs +53 -0
- data/sig/ibex/racc_migration.rbs +8 -0
- data/sig/ibex/rake_task.rbs +51 -0
- data/sig/ibex/samples.rbs +51 -0
- data/sig/ibex/table_simulation/result.rbs +32 -0
- data/sig/ibex/table_simulation/simulator.rbs +98 -0
- data/sig/ibex/table_simulation/step.rbs +40 -0
- data/sig/ibex/table_simulation/text.rbs +14 -0
- data/sig/ibex/table_simulation.rbs +7 -0
- data/sig/ibex/tables.rbs +0 -26
- data/sig/ibex/watch/runner.rbs +48 -0
- data/sig/ibex/watch/source_snapshot.rbs +42 -0
- data/sig/ibex/watch.rbs +7 -0
- data/sig/ibex.rbs +2 -0
- metadata +301 -16
- data/.rubocop.yml +0 -43
- data/CHANGELOG.md +0 -30
- data/Rakefile +0 -25
- data/Steepfile +0 -10
- data/docs/compat-notes.md +0 -37
- data/docs/lexer-coverage.md +0 -14
- data/docs/phase10-extensions.md +0 -27
- data/gemfiles/Gemfile +0 -7
- data/gemfiles/Gemfile.lock +0 -98
- data/lib/ibex/frontend/grammar.y +0 -156
- data/lib/ibex/runtime/parser.rb +0 -360
- data/lib/ibex/runtime.rb +0 -8
- data/sig/ibex/runtime/parser.rbs +0 -167
- data/sig/ibex/runtime.rbs +0 -6
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Coverage
|
|
6
|
+
# Strictly turns one or more complete parse sessions into a coverage report.
|
|
7
|
+
class Collector
|
|
8
|
+
# @rbs @grammar_digest: String?
|
|
9
|
+
# @rbs @table_format_version: Integer?
|
|
10
|
+
# @rbs @state_count: Integer?
|
|
11
|
+
# @rbs @production_count: Integer?
|
|
12
|
+
# @rbs @sessions: Integer
|
|
13
|
+
# @rbs @event_count: Integer
|
|
14
|
+
# @rbs @state_hits: Hash[Integer, Integer]
|
|
15
|
+
# @rbs @production_hits: Hash[Integer, Integer]
|
|
16
|
+
# @rbs @in_session: bool
|
|
17
|
+
# @rbs @expected_sequence: Integer
|
|
18
|
+
|
|
19
|
+
# @rbs () -> void
|
|
20
|
+
def initialize
|
|
21
|
+
@grammar_digest = nil
|
|
22
|
+
@table_format_version = nil
|
|
23
|
+
@state_count = nil
|
|
24
|
+
@production_count = nil
|
|
25
|
+
@sessions = 0
|
|
26
|
+
@event_count = 0
|
|
27
|
+
@state_hits = Hash.new(0)
|
|
28
|
+
@production_hits = Hash.new(0)
|
|
29
|
+
@in_session = false
|
|
30
|
+
@expected_sequence = 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs (String path) -> Report
|
|
34
|
+
def self.collect_file(path)
|
|
35
|
+
collector = new
|
|
36
|
+
EventStream.each_file(path) do |document, line|
|
|
37
|
+
collector.consume(document, source: path, line: line)
|
|
38
|
+
end
|
|
39
|
+
collector.finish(source: path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs (Hash[String, untyped] document, source: String, line: Integer) -> void
|
|
43
|
+
def consume(document, source:, line:)
|
|
44
|
+
event = document.fetch("event")
|
|
45
|
+
sequence = document.fetch("sequence")
|
|
46
|
+
if event == "start"
|
|
47
|
+
start_session(document.fetch("data"), sequence, source, line)
|
|
48
|
+
else
|
|
49
|
+
consume_session_event(event, document.fetch("data"), sequence, source, line)
|
|
50
|
+
end
|
|
51
|
+
@event_count += 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @rbs (source: String) -> Report
|
|
55
|
+
def finish(source:)
|
|
56
|
+
invalid(source, 1, "event stream ended before accept or reject") if @in_session
|
|
57
|
+
invalid(source, 1, "event stream does not contain a parse session") if @sessions.zero?
|
|
58
|
+
Report.new(
|
|
59
|
+
grammar_digest: required_metadata(@grammar_digest),
|
|
60
|
+
table_format_version: required_metadata(@table_format_version),
|
|
61
|
+
state_count: required_metadata(@state_count),
|
|
62
|
+
production_count: required_metadata(@production_count),
|
|
63
|
+
sessions: @sessions,
|
|
64
|
+
event_count: @event_count,
|
|
65
|
+
state_hits: @state_hits,
|
|
66
|
+
production_hits: @production_hits
|
|
67
|
+
)
|
|
68
|
+
rescue ArgumentError => e
|
|
69
|
+
raise Ibex::Error, "#{source}:1:1: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# @rbs (Hash[untyped, untyped] data, untyped sequence, String source, Integer line) -> void
|
|
75
|
+
def start_session(data, sequence, source, line)
|
|
76
|
+
invalid(source, line, "new start event before prior session ended") if @in_session
|
|
77
|
+
invalid(source, line, "start event sequence must be 1") unless sequence == 1
|
|
78
|
+
|
|
79
|
+
metadata, initial_state = session_metadata(data, source, line)
|
|
80
|
+
establish_metadata(metadata, source, line)
|
|
81
|
+
validate_metadata!(source, line)
|
|
82
|
+
hit_state(initial_state, source, line)
|
|
83
|
+
@sessions += 1
|
|
84
|
+
@in_session = true
|
|
85
|
+
@expected_sequence = 2
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @rbs (String event, Hash[untyped, untyped] data, untyped sequence, String source, Integer line) -> void
|
|
89
|
+
def consume_session_event(event, data, sequence, source, line)
|
|
90
|
+
invalid(source, line, "event appears outside a parse session") unless @in_session
|
|
91
|
+
unless sequence == @expected_sequence
|
|
92
|
+
invalid(source, line, "expected event sequence #{@expected_sequence}, got #{sequence.inspect}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
case event
|
|
96
|
+
when "shift", "recover" then hit_state(data["state"], source, line)
|
|
97
|
+
when "reduce"
|
|
98
|
+
hit_production(data["production_id"], source, line)
|
|
99
|
+
hit_state(data["goto_state"], source, line)
|
|
100
|
+
end
|
|
101
|
+
if %w[accept reject].include?(event)
|
|
102
|
+
@in_session = false
|
|
103
|
+
@expected_sequence = 1
|
|
104
|
+
else
|
|
105
|
+
@expected_sequence += 1
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs (Hash[untyped, untyped] data, String source, Integer line)
|
|
110
|
+
# -> [[String, Integer, Integer, Integer], Integer]
|
|
111
|
+
def session_metadata(data, source, line)
|
|
112
|
+
digest = data["grammar_digest"]
|
|
113
|
+
format = data["table_format_version"]
|
|
114
|
+
states = data["state_count"]
|
|
115
|
+
productions = data["production_count"]
|
|
116
|
+
initial_state = data["initial_state"]
|
|
117
|
+
unless digest.is_a?(String) && format.is_a?(Integer) && states.is_a?(Integer) &&
|
|
118
|
+
productions.is_a?(Integer) && initial_state.is_a?(Integer)
|
|
119
|
+
invalid(source, line, "start event lacks generated parser coverage metadata")
|
|
120
|
+
end
|
|
121
|
+
[[digest, format, states, productions], initial_state]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @rbs ([String, Integer, Integer, Integer] metadata, String source, Integer line) -> void
|
|
125
|
+
def establish_metadata(metadata, source, line)
|
|
126
|
+
unless @grammar_digest
|
|
127
|
+
@grammar_digest, @table_format_version, @state_count, @production_count = metadata
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
expected = [@grammar_digest, @table_format_version, @state_count, @production_count]
|
|
132
|
+
invalid(source, line, "parse sessions use different parser metadata") unless metadata == expected
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs (String source, Integer line) -> void
|
|
136
|
+
def validate_metadata!(source, line)
|
|
137
|
+
Report.new(
|
|
138
|
+
grammar_digest: required_metadata(@grammar_digest),
|
|
139
|
+
table_format_version: required_metadata(@table_format_version),
|
|
140
|
+
state_count: required_metadata(@state_count),
|
|
141
|
+
production_count: required_metadata(@production_count),
|
|
142
|
+
sessions: 1,
|
|
143
|
+
event_count: 1,
|
|
144
|
+
state_hits: {},
|
|
145
|
+
production_hits: {}
|
|
146
|
+
)
|
|
147
|
+
rescue ArgumentError => e
|
|
148
|
+
invalid(source, line, e.message)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @rbs (untyped id, String source, Integer line) -> void
|
|
152
|
+
def hit_state(id, source, line)
|
|
153
|
+
total = required_metadata(@state_count)
|
|
154
|
+
invalid(source, line, "state id #{id.inspect} is outside 0...#{total}") unless valid_id?(id, total)
|
|
155
|
+
increment(@state_hits, id, "state", source, line)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @rbs (untyped id, String source, Integer line) -> void
|
|
159
|
+
def hit_production(id, source, line)
|
|
160
|
+
total = required_metadata(@production_count)
|
|
161
|
+
invalid(source, line, "production id #{id.inspect} is outside 0...#{total}") unless valid_id?(id, total)
|
|
162
|
+
increment(@production_hits, id, "production", source, line)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @rbs (untyped id, Integer total) -> bool
|
|
166
|
+
def valid_id?(id, total)
|
|
167
|
+
id.is_a?(Integer) && id >= 0 && id < total
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @rbs (Hash[Integer, Integer] hits, Integer id, String kind, String source, Integer line) -> void
|
|
171
|
+
def increment(hits, id, kind, source, line)
|
|
172
|
+
count = hits[id] + 1
|
|
173
|
+
invalid(source, line, "#{kind} hit count exceeds the supported count") if count > Report::MAX_COUNT
|
|
174
|
+
hits[id] = count
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @rbs [T] (T? value) -> T
|
|
178
|
+
def required_metadata(value)
|
|
179
|
+
value || raise(ArgumentError, "coverage metadata is unavailable")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @rbs (String source, Integer line, String message) -> bot
|
|
183
|
+
def invalid(source, line, message)
|
|
184
|
+
raise Ibex::Error, "#{source}:#{line}:1: #{message}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../error"
|
|
6
|
+
require_relative "../runtime/event"
|
|
7
|
+
require_relative "runtime_event_validator"
|
|
8
|
+
|
|
9
|
+
module Ibex
|
|
10
|
+
module Coverage
|
|
11
|
+
# Bounded reader for the public runtime-event JSON Lines protocol.
|
|
12
|
+
module EventStream
|
|
13
|
+
# @rbs!
|
|
14
|
+
# interface _CoverageEventInput
|
|
15
|
+
# def gets: (Integer) -> String?
|
|
16
|
+
# end
|
|
17
|
+
|
|
18
|
+
MAX_LINE_BYTES = 1_048_576 #: Integer
|
|
19
|
+
MAX_NESTING = 32 #: Integer
|
|
20
|
+
ROOT_KEYS = %w[data event ibex_runtime_event schema_version sequence].freeze #: Array[String]
|
|
21
|
+
EVENT_TYPES = %w[start shift reduce error recover discard accept reject].freeze #: Array[String]
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# @rbs (String path) { (Hash[String, untyped], Integer) -> void } -> void
|
|
25
|
+
def each_file(path, &block)
|
|
26
|
+
File.open(path, "rb") { |input| each_io(input, source: path, &block) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @rbs (_CoverageEventInput input, source: String)
|
|
30
|
+
# { (Hash[String, untyped], Integer) -> void } -> void
|
|
31
|
+
def each_io(input, source:, &block)
|
|
32
|
+
line_number = 0
|
|
33
|
+
while (line = input.gets(MAX_LINE_BYTES + 1))
|
|
34
|
+
line_number += 1
|
|
35
|
+
reject_oversized_line(line, source, line_number)
|
|
36
|
+
document = parse_line(line, source, line_number)
|
|
37
|
+
validate_envelope(document, source, line_number)
|
|
38
|
+
block.call(document, line_number)
|
|
39
|
+
end
|
|
40
|
+
raise_invalid(source, 1, "event stream is empty") if line_number.zero?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# @rbs (String line, String source, Integer line_number) -> void
|
|
46
|
+
def reject_oversized_line(line, source, line_number)
|
|
47
|
+
return if line.bytesize <= MAX_LINE_BYTES
|
|
48
|
+
|
|
49
|
+
raise_invalid(source, line_number, "event line exceeds #{MAX_LINE_BYTES} bytes")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @rbs (String line, String source, Integer line_number) -> Hash[String, untyped]
|
|
53
|
+
def parse_line(line, source, line_number)
|
|
54
|
+
text = line.dup.force_encoding(Encoding::UTF_8)
|
|
55
|
+
raise_invalid(source, line_number, "event line is not valid UTF-8") unless text.valid_encoding?
|
|
56
|
+
|
|
57
|
+
value = JSON.parse(text, max_nesting: MAX_NESTING, allow_nan: false)
|
|
58
|
+
return value if value.is_a?(Hash) && value.keys.all?(String)
|
|
59
|
+
|
|
60
|
+
raise_invalid(source, line_number, "event line must be a JSON object")
|
|
61
|
+
rescue JSON::ParserError => e
|
|
62
|
+
raise_invalid(source, line_number, "invalid event JSON: #{e.message}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @rbs (Hash[String, untyped] document, String source, Integer line_number) -> void
|
|
66
|
+
def validate_envelope(document, source, line_number)
|
|
67
|
+
unless document.keys.sort == ROOT_KEYS
|
|
68
|
+
raise_invalid(source, line_number, "event object has unknown or missing fields")
|
|
69
|
+
end
|
|
70
|
+
unless document["ibex_runtime_event"] == Runtime::Event::IDENTIFIER &&
|
|
71
|
+
document["schema_version"] == Runtime::Event::SCHEMA_VERSION
|
|
72
|
+
raise_invalid(source, line_number, "unsupported runtime event schema")
|
|
73
|
+
end
|
|
74
|
+
sequence = document["sequence"]
|
|
75
|
+
raise_invalid(source, line_number, "event sequence must be positive") unless positive_integer?(sequence)
|
|
76
|
+
unless EVENT_TYPES.include?(document["event"])
|
|
77
|
+
raise_invalid(source, line_number, "unknown runtime event type")
|
|
78
|
+
end
|
|
79
|
+
raise_invalid(source, line_number, "event data must be an object") unless document["data"].is_a?(Hash)
|
|
80
|
+
return if RuntimeEventValidator.valid?(document)
|
|
81
|
+
|
|
82
|
+
raise_invalid(source, line_number, "event data does not match the runtime-event v1 schema")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs (untyped value) -> bool
|
|
86
|
+
def positive_integer?(value)
|
|
87
|
+
value.is_a?(Integer) && value.positive?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @rbs (String source, Integer line_number, String message) -> bot
|
|
91
|
+
def raise_invalid(source, line_number, message)
|
|
92
|
+
raise Ibex::Error, "#{source}:#{line_number}:1: #{message}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../error"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
module Coverage
|
|
9
|
+
# Versioned, mergeable runtime coverage result.
|
|
10
|
+
class Report
|
|
11
|
+
IDENTIFIER = "runtime-coverage" #: String
|
|
12
|
+
SCHEMA_VERSION = 1 #: Integer
|
|
13
|
+
MAX_DOCUMENT_BYTES = 16_777_216 #: Integer
|
|
14
|
+
MAX_TOTAL = 1_000_000 #: Integer
|
|
15
|
+
MAX_COUNT = 9_223_372_036_854_775_807 #: Integer
|
|
16
|
+
ROOT_KEYS = %w[events grammar_digest ibex_coverage production_hits schema_version sessions state_hits
|
|
17
|
+
table_format_version totals].freeze #: Array[String]
|
|
18
|
+
TOTAL_KEYS = %w[productions states].freeze #: Array[String]
|
|
19
|
+
HIT_KEYS = %w[count id].freeze #: Array[String]
|
|
20
|
+
|
|
21
|
+
attr_reader :grammar_digest #: String
|
|
22
|
+
attr_reader :table_format_version #: Integer
|
|
23
|
+
attr_reader :state_count #: Integer
|
|
24
|
+
attr_reader :production_count #: Integer
|
|
25
|
+
attr_reader :sessions #: Integer
|
|
26
|
+
attr_reader :event_count #: Integer
|
|
27
|
+
attr_reader :state_hits #: Hash[Integer, Integer]
|
|
28
|
+
attr_reader :production_hits #: Hash[Integer, Integer]
|
|
29
|
+
|
|
30
|
+
# rubocop:disable Layout/LineLength
|
|
31
|
+
# @rbs (grammar_digest: String, table_format_version: Integer, state_count: Integer, production_count: Integer, sessions: Integer, event_count: Integer, state_hits: Hash[Integer, Integer], production_hits: Hash[Integer, Integer]) -> void
|
|
32
|
+
def initialize(grammar_digest:, table_format_version:, state_count:, production_count:, sessions:, event_count:,
|
|
33
|
+
state_hits:, production_hits:)
|
|
34
|
+
@grammar_digest = validate_digest(grammar_digest)
|
|
35
|
+
@table_format_version = positive_integer(table_format_version, "table_format_version")
|
|
36
|
+
@state_count = total_integer(state_count, "state total", minimum: 1)
|
|
37
|
+
@production_count = total_integer(production_count, "production total", minimum: 0)
|
|
38
|
+
@sessions = positive_integer(sessions, "sessions")
|
|
39
|
+
@event_count = positive_integer(event_count, "events")
|
|
40
|
+
raise ArgumentError, "events must be at least sessions" if @event_count < @sessions
|
|
41
|
+
|
|
42
|
+
@state_hits = validate_hits(state_hits, @state_count, "state").freeze
|
|
43
|
+
@production_hits = validate_hits(production_hits, @production_count, "production").freeze
|
|
44
|
+
validate_event_bounds
|
|
45
|
+
freeze
|
|
46
|
+
end
|
|
47
|
+
# rubocop:enable Layout/LineLength
|
|
48
|
+
|
|
49
|
+
# @rbs () -> Hash[String, untyped]
|
|
50
|
+
def to_h
|
|
51
|
+
{
|
|
52
|
+
"ibex_coverage" => IDENTIFIER,
|
|
53
|
+
"schema_version" => SCHEMA_VERSION,
|
|
54
|
+
"grammar_digest" => @grammar_digest,
|
|
55
|
+
"table_format_version" => @table_format_version,
|
|
56
|
+
"totals" => { "states" => @state_count, "productions" => @production_count },
|
|
57
|
+
"sessions" => @sessions,
|
|
58
|
+
"events" => @event_count,
|
|
59
|
+
"state_hits" => hit_documents(@state_hits),
|
|
60
|
+
"production_hits" => hit_documents(@production_hits)
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs (*untyped) -> String
|
|
65
|
+
def to_json(*)
|
|
66
|
+
"#{JSON.pretty_generate(to_h)}\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs (String path) -> Report
|
|
70
|
+
def self.load_file(path)
|
|
71
|
+
size = File.size(path)
|
|
72
|
+
if size > MAX_DOCUMENT_BYTES
|
|
73
|
+
raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
source = File.binread(path)
|
|
77
|
+
if source.bytesize > MAX_DOCUMENT_BYTES
|
|
78
|
+
raise Ibex::Error, "#{path}:1:1: coverage document exceeds #{MAX_DOCUMENT_BYTES} bytes"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
source.force_encoding(Encoding::UTF_8)
|
|
82
|
+
raise Ibex::Error, "#{path}:1:1: coverage document is not valid UTF-8" unless source.valid_encoding?
|
|
83
|
+
|
|
84
|
+
value = JSON.parse(source, max_nesting: 16, allow_nan: false)
|
|
85
|
+
from_h(value, source: path)
|
|
86
|
+
rescue JSON::ParserError => e
|
|
87
|
+
raise Ibex::Error, "#{path}:1:1: invalid coverage JSON: #{e.message}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @rbs (untyped value, source: String) -> Report
|
|
91
|
+
def self.from_h(value, source:)
|
|
92
|
+
document = string_hash(value, source, "coverage document")
|
|
93
|
+
invalid(source, "coverage object has unknown or missing fields") unless document.keys.sort == ROOT_KEYS
|
|
94
|
+
valid_schema = document["ibex_coverage"] == IDENTIFIER &&
|
|
95
|
+
document["schema_version"] == SCHEMA_VERSION
|
|
96
|
+
invalid(source, "unsupported coverage schema") unless valid_schema
|
|
97
|
+
totals = string_hash(document["totals"], source, "coverage totals")
|
|
98
|
+
invalid(source, "coverage totals have unknown or missing fields") unless totals.keys.sort == TOTAL_KEYS
|
|
99
|
+
|
|
100
|
+
state_count = totals["states"]
|
|
101
|
+
production_count = totals["productions"]
|
|
102
|
+
new(
|
|
103
|
+
grammar_digest: document["grammar_digest"],
|
|
104
|
+
table_format_version: document["table_format_version"],
|
|
105
|
+
state_count: state_count,
|
|
106
|
+
production_count: production_count,
|
|
107
|
+
sessions: document["sessions"],
|
|
108
|
+
event_count: document["events"],
|
|
109
|
+
state_hits: parse_hits(document["state_hits"], state_count, source, "state"),
|
|
110
|
+
production_hits: parse_hits(document["production_hits"], production_count, source, "production")
|
|
111
|
+
)
|
|
112
|
+
rescue ArgumentError => e
|
|
113
|
+
raise Ibex::Error, "#{source}:1:1: #{e.message}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @rbs (Array[Report] reports) -> Report
|
|
117
|
+
def self.merge(reports)
|
|
118
|
+
raise ArgumentError, "at least one coverage report is required" if reports.empty?
|
|
119
|
+
|
|
120
|
+
first = reports.first
|
|
121
|
+
reports.drop(1).each { |report| ensure_compatible(first, report) }
|
|
122
|
+
new(
|
|
123
|
+
grammar_digest: first.grammar_digest,
|
|
124
|
+
table_format_version: first.table_format_version,
|
|
125
|
+
state_count: first.state_count,
|
|
126
|
+
production_count: first.production_count,
|
|
127
|
+
sessions: checked_sum(reports.map(&:sessions), "sessions"),
|
|
128
|
+
event_count: checked_sum(reports.map(&:event_count), "events"),
|
|
129
|
+
state_hits: merge_hits(reports.map(&:state_hits), "state"),
|
|
130
|
+
production_hits: merge_hits(reports.map(&:production_hits), "production")
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# @rbs (untyped input) -> String
|
|
137
|
+
def validate_digest(input)
|
|
138
|
+
valid = input.is_a?(String) && input.match?(/\Asha256:[0-9a-f]{64}\z/)
|
|
139
|
+
return input.dup.freeze if valid
|
|
140
|
+
|
|
141
|
+
raise ArgumentError, "grammar_digest must be a full lowercase SHA-256 digest"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @rbs (untyped input, String name) -> Integer
|
|
145
|
+
def positive_integer(input, name)
|
|
146
|
+
return input if input.is_a?(Integer) && input.positive? && input <= MAX_COUNT
|
|
147
|
+
|
|
148
|
+
raise ArgumentError, "#{name} must be a bounded positive integer"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @rbs (untyped input, String name, minimum: Integer) -> Integer
|
|
152
|
+
def total_integer(input, name, minimum:)
|
|
153
|
+
return input if input.is_a?(Integer) && input >= minimum && input <= MAX_TOTAL
|
|
154
|
+
|
|
155
|
+
raise ArgumentError, "#{name} must be between #{minimum} and #{MAX_TOTAL}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @rbs (Hash[Integer, Integer] input, Integer total, String kind) -> Hash[Integer, Integer]
|
|
159
|
+
def validate_hits(input, total, kind)
|
|
160
|
+
input.to_h do |id, count|
|
|
161
|
+
unless id.is_a?(Integer) && id >= 0 && id < total
|
|
162
|
+
raise ArgumentError, "#{kind} hit id #{id.inspect} is outside 0...#{total}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
[id, positive_integer(count, "#{kind} hit count")]
|
|
166
|
+
end.sort.to_h
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @rbs (Hash[Integer, Integer] hits) -> Array[Hash[String, Integer]]
|
|
170
|
+
def hit_documents(hits)
|
|
171
|
+
hits.map { |id, count| { "id" => id, "count" => count } }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @rbs () -> void
|
|
175
|
+
def validate_event_bounds
|
|
176
|
+
raise ArgumentError, "state hit counts exceed events" if @state_hits.values.sum > @event_count
|
|
177
|
+
return unless @production_hits.values.sum > @event_count
|
|
178
|
+
|
|
179
|
+
raise ArgumentError, "production hit counts exceed events"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class << self
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
# @rbs (untyped value, String source, String name) -> Hash[String, untyped]
|
|
186
|
+
def string_hash(value, source, name)
|
|
187
|
+
return value if value.is_a?(Hash) && value.keys.all?(String)
|
|
188
|
+
|
|
189
|
+
invalid(source, "#{name} must be an object")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @rbs (untyped value, untyped total, String source, String kind) -> Hash[Integer, Integer]
|
|
193
|
+
def parse_hits(value, total, source, kind)
|
|
194
|
+
invalid(source, "#{kind}_hits must be an array") unless value.is_a?(Array)
|
|
195
|
+
hits = {} #: Hash[Integer, Integer]
|
|
196
|
+
previous = -1
|
|
197
|
+
value.each do |entry|
|
|
198
|
+
id, count = parse_hit(entry, total, previous, source, kind)
|
|
199
|
+
hits[id] = count
|
|
200
|
+
previous = id
|
|
201
|
+
end
|
|
202
|
+
hits
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @rbs (untyped entry, untyped total, Integer previous, String source, String kind) -> [Integer, Integer]
|
|
206
|
+
def parse_hit(entry, total, previous, source, kind)
|
|
207
|
+
hit = string_hash(entry, source, "#{kind} hit")
|
|
208
|
+
invalid(source, "#{kind} hit has unknown or missing fields") unless hit.keys.sort == HIT_KEYS
|
|
209
|
+
id = hit["id"]
|
|
210
|
+
count = hit["count"]
|
|
211
|
+
unless id.is_a?(Integer) && id > previous
|
|
212
|
+
invalid(source, "#{kind} hits must have unique, ascending integer ids")
|
|
213
|
+
end
|
|
214
|
+
unless total.is_a?(Integer) && id >= 0 && id < total
|
|
215
|
+
invalid(source, "#{kind} hit id is outside the declared total")
|
|
216
|
+
end
|
|
217
|
+
unless count.is_a?(Integer) && count.positive? && count <= MAX_COUNT
|
|
218
|
+
invalid(source, "#{kind} hit count must be positive")
|
|
219
|
+
end
|
|
220
|
+
[id, count]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @rbs (Report expected, Report actual) -> void
|
|
224
|
+
def ensure_compatible(expected, actual)
|
|
225
|
+
fields = %i[grammar_digest table_format_version state_count production_count]
|
|
226
|
+
mismatch = fields.find { |field| expected.public_send(field) != actual.public_send(field) }
|
|
227
|
+
return unless mismatch
|
|
228
|
+
|
|
229
|
+
raise ArgumentError, "coverage reports have different #{mismatch}"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# @rbs (Array[Integer] values, String name) -> Integer
|
|
233
|
+
def checked_sum(values, name)
|
|
234
|
+
total = values.sum
|
|
235
|
+
raise ArgumentError, "merged #{name} exceed the supported count" if total > MAX_COUNT
|
|
236
|
+
|
|
237
|
+
total
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @rbs (Array[Hash[Integer, Integer]] collections, String kind) -> Hash[Integer, Integer]
|
|
241
|
+
def merge_hits(collections, kind)
|
|
242
|
+
result = Hash.new(0) #: Hash[Integer, Integer]
|
|
243
|
+
collections.each do |hits|
|
|
244
|
+
hits.each do |id, count|
|
|
245
|
+
result[id] += count
|
|
246
|
+
raise ArgumentError, "merged #{kind} hit count exceeds the supported count" if result[id] > MAX_COUNT
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
result
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @rbs (String source, String message) -> bot
|
|
253
|
+
def invalid(source, message)
|
|
254
|
+
raise Ibex::Error, "#{source}:1:1: #{message}"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|