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,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Coverage
|
|
6
|
+
# Dependency-free structural validation for runtime-event schema version 1.
|
|
7
|
+
module RuntimeEventValidator
|
|
8
|
+
TOKEN_KEYS = %w[location state token token_id value].freeze #: Array[String]
|
|
9
|
+
LOCATION_KEYS = %w[column end_column end_line file line].freeze #: Array[String]
|
|
10
|
+
VALIDATORS = {
|
|
11
|
+
"start" => :valid_start?,
|
|
12
|
+
"shift" => :valid_shift?,
|
|
13
|
+
"reduce" => :valid_reduce?,
|
|
14
|
+
"error" => :valid_error?,
|
|
15
|
+
"recover" => :valid_recover?,
|
|
16
|
+
"discard" => :valid_discard?,
|
|
17
|
+
"accept" => :valid_accept?,
|
|
18
|
+
"reject" => :valid_reject?
|
|
19
|
+
}.freeze #: Hash[String, Symbol]
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# @rbs (Hash[String, untyped] document) -> bool
|
|
23
|
+
def valid?(document)
|
|
24
|
+
data = document["data"]
|
|
25
|
+
return false unless data.is_a?(Hash) && data.keys.all?(String)
|
|
26
|
+
|
|
27
|
+
validator = VALIDATORS[document["event"]]
|
|
28
|
+
validator ? send(validator, data) : false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
34
|
+
def valid_start?(data)
|
|
35
|
+
return false unless exact_keys?(
|
|
36
|
+
data,
|
|
37
|
+
%w[driver grammar_digest initial_state production_count state_count table_format_version]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
integer_fields = %w[initial_state table_format_version]
|
|
41
|
+
optional_integer_fields = %w[state_count production_count]
|
|
42
|
+
%w[pull push].include?(data["driver"]) &&
|
|
43
|
+
integer_fields.all? { |key| integer?(data[key]) } &&
|
|
44
|
+
optional_integer_fields.all? { |key| optional_integer?(data[key]) } &&
|
|
45
|
+
optional_string?(data["grammar_digest"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
49
|
+
def valid_shift?(data)
|
|
50
|
+
exact_keys?(data, TOKEN_KEYS + %w[from_state]) &&
|
|
51
|
+
valid_token_fields?(data) &&
|
|
52
|
+
integer?(data["from_state"])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
56
|
+
def valid_reduce?(data)
|
|
57
|
+
keys = %w[goto_state lhs location post_state pre_state production_id result rhs_length]
|
|
58
|
+
exact_keys?(data, keys) &&
|
|
59
|
+
%w[goto_state lhs post_state pre_state production_id].all? { |key| integer?(data[key]) } &&
|
|
60
|
+
integer?(data["rhs_length"]) && data["rhs_length"] >= 0 &&
|
|
61
|
+
summary?(data["result"]) && location?(data["location"])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs (Hash[String, untyped] data, reasons: Array[String]) -> bool
|
|
65
|
+
def valid_token_event?(data, reasons:)
|
|
66
|
+
exact_keys?(data, TOKEN_KEYS + %w[reason]) &&
|
|
67
|
+
valid_token_fields?(data) &&
|
|
68
|
+
reasons.include?(data["reason"])
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
72
|
+
def valid_error?(data)
|
|
73
|
+
valid_token_event?(data, reasons: %w[syntax semantic])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
77
|
+
def valid_discard?(data)
|
|
78
|
+
valid_token_event?(data, reasons: %w[recovery])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
82
|
+
def valid_reject?(data)
|
|
83
|
+
valid_token_event?(data, reasons: %w[eof_during_recovery no_recovery_state])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
87
|
+
def valid_recover?(data)
|
|
88
|
+
exact_keys?(data, TOKEN_KEYS + %w[from_state reason]) &&
|
|
89
|
+
valid_token_fields?(data) &&
|
|
90
|
+
integer?(data["from_state"]) &&
|
|
91
|
+
%w[syntax semantic].include?(data["reason"])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
95
|
+
def valid_accept?(data)
|
|
96
|
+
exact_keys?(data, %w[reason result state]) &&
|
|
97
|
+
integer?(data["state"]) &&
|
|
98
|
+
summary?(data["result"]) &&
|
|
99
|
+
%w[table semantic].include?(data["reason"])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @rbs (Hash[String, untyped] data) -> bool
|
|
103
|
+
def valid_token_fields?(data)
|
|
104
|
+
integer?(data["state"]) &&
|
|
105
|
+
optional_integer?(data["token_id"]) &&
|
|
106
|
+
summary?(data["token"]) &&
|
|
107
|
+
summary?(data["value"]) &&
|
|
108
|
+
location?(data["location"])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @rbs (untyped value) -> bool
|
|
112
|
+
def summary?(value)
|
|
113
|
+
case value
|
|
114
|
+
when nil, true, false, Integer then true
|
|
115
|
+
when Float then value.finite?
|
|
116
|
+
when String then value.length <= 256
|
|
117
|
+
else composite_summary?(value)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs (untyped value) -> bool
|
|
122
|
+
def composite_summary?(value)
|
|
123
|
+
return value.length <= 17 && value.all? { |entry| summary?(entry) } if value.is_a?(Array)
|
|
124
|
+
return false unless value.is_a?(Hash)
|
|
125
|
+
|
|
126
|
+
value.keys.all?(String) && value.length <= 17 && value.values.all? { |entry| summary?(entry) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @rbs (untyped value) -> bool
|
|
130
|
+
def location?(value)
|
|
131
|
+
return true if value.nil?
|
|
132
|
+
return false unless value.is_a?(Hash) && value.keys.all?(String)
|
|
133
|
+
return value["unavailable"].is_a?(String) if value.keys == ["unavailable"]
|
|
134
|
+
|
|
135
|
+
(value.keys - LOCATION_KEYS).empty? && value.values.all? { |entry| summary?(entry) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs (Hash[String, untyped] data, Array[String] keys) -> bool
|
|
139
|
+
def exact_keys?(data, keys)
|
|
140
|
+
data.keys.sort == keys.sort
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs (untyped value) -> bool
|
|
144
|
+
def integer?(value)
|
|
145
|
+
value.is_a?(Integer)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs (untyped value) -> bool
|
|
149
|
+
def optional_integer?(value)
|
|
150
|
+
value.nil? || integer?(value)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs (untyped value) -> bool
|
|
154
|
+
def optional_string?(value)
|
|
155
|
+
value.nil? || value.is_a?(String)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "coverage/runtime_event_validator"
|
|
5
|
+
require_relative "coverage/event_stream"
|
|
6
|
+
require_relative "coverage/report"
|
|
7
|
+
require_relative "coverage/collector"
|
|
8
|
+
|
|
9
|
+
module Ibex
|
|
10
|
+
# Deterministic state and production coverage derived from runtime event streams.
|
|
11
|
+
module Coverage
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "parser_v2"
|
|
4
|
+
|
|
5
|
+
module Ibex
|
|
6
|
+
module ErrorMessages
|
|
7
|
+
# Strict line-oriented parser for the ibex-messages v1 and v2 formats.
|
|
8
|
+
class Parser
|
|
9
|
+
include ParserV2
|
|
10
|
+
|
|
11
|
+
ESCAPES = { "\\" => "\\", "n" => "\n", "t" => "\t", "r" => "\r" }.freeze
|
|
12
|
+
|
|
13
|
+
# @rbs @file: String
|
|
14
|
+
# @rbs @lines: Array[String]
|
|
15
|
+
|
|
16
|
+
# @rbs (String source, file: String) -> void
|
|
17
|
+
def initialize(source, file:)
|
|
18
|
+
@file = file
|
|
19
|
+
text = source.dup.force_encoding(Encoding::UTF_8)
|
|
20
|
+
fail_at(1, 1, "messages file must be valid UTF-8") unless text.valid_encoding?
|
|
21
|
+
|
|
22
|
+
@lines = text.lines(chomp: true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs () -> Document
|
|
26
|
+
def parse
|
|
27
|
+
version = validate_header
|
|
28
|
+
return parse_v2 if version == 2
|
|
29
|
+
|
|
30
|
+
parse_v1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# @rbs () -> Document
|
|
36
|
+
def parse_v1
|
|
37
|
+
entries = [] #: Array[Entry]
|
|
38
|
+
declarations = {} #: Hash[Integer, Integer]
|
|
39
|
+
index = 1
|
|
40
|
+
while index < @lines.length
|
|
41
|
+
line = @lines.fetch(index)
|
|
42
|
+
if ignorable?(line)
|
|
43
|
+
index += 1
|
|
44
|
+
next
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
match = line.strip.match(/\A(state|removed)\s+([0-9]+)\z/)
|
|
48
|
+
fail_at(index + 1, column(line), top_level_expectation) unless match
|
|
49
|
+
state_text = match[2] || raise(Ibex::Error, "missing state number")
|
|
50
|
+
state = Integer(state_text, 10)
|
|
51
|
+
reject_duplicate(declarations, state, index, line)
|
|
52
|
+
declarations[state] = index + 1
|
|
53
|
+
entry, index = parse_entry(index, state, match[1] == "state" ? :active : :removed)
|
|
54
|
+
entries << entry
|
|
55
|
+
end
|
|
56
|
+
Document.new(version: 1, entries: entries)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @rbs () -> Integer
|
|
60
|
+
def validate_header
|
|
61
|
+
header = @lines.first&.delete_prefix("\uFEFF")
|
|
62
|
+
return 1 if header == HEADER_V1
|
|
63
|
+
return 2 if header == HEADER
|
|
64
|
+
|
|
65
|
+
fail_at(1, 1, "expected #{HEADER.inspect} or #{HEADER_V1.inspect}")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @rbs (Hash[Integer, Integer] declarations, Integer state, Integer index, String line) -> void
|
|
69
|
+
def reject_duplicate(declarations, state, index, line)
|
|
70
|
+
return unless declarations.key?(state)
|
|
71
|
+
|
|
72
|
+
fail_at(index + 1, column(line),
|
|
73
|
+
"duplicate state #{state}; first declared at line #{declarations.fetch(state)}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @rbs (Integer opening_index, Integer state, :active | :removed status) -> [Entry, Integer]
|
|
77
|
+
def parse_entry(opening_index, state, status)
|
|
78
|
+
message_lines = [] #: Array[String]
|
|
79
|
+
index = opening_index + 1
|
|
80
|
+
while index < @lines.length
|
|
81
|
+
line = @lines.fetch(index)
|
|
82
|
+
return close_entry(message_lines, opening_index, index, state, status) if line.strip == "end"
|
|
83
|
+
|
|
84
|
+
read_entry_line(message_lines, line, index)
|
|
85
|
+
index += 1
|
|
86
|
+
end
|
|
87
|
+
label = status == :active ? "state" : "removed"
|
|
88
|
+
fail_at(opening_index + 1, 1, "unterminated #{label} #{state} entry")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs (Array[String] message_lines, Integer opening_index, Integer index, Integer state,
|
|
92
|
+
# :active | :removed status) -> [Entry, Integer]
|
|
93
|
+
def close_entry(message_lines, opening_index, index, state, status)
|
|
94
|
+
message = message_lines.empty? ? nil : message_lines.join("\n")
|
|
95
|
+
fail_at(opening_index + 1, 1, "message for state #{state} must not be empty") if message&.strip&.empty?
|
|
96
|
+
|
|
97
|
+
entry = Entry.new(state: state, status: status, message: message, line: opening_index + 1)
|
|
98
|
+
[entry, index + 1]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs (Array[String] message_lines, String line, Integer index) -> void
|
|
102
|
+
def read_entry_line(message_lines, line, index)
|
|
103
|
+
return if ignorable?(line)
|
|
104
|
+
|
|
105
|
+
unless line.start_with?("|")
|
|
106
|
+
fail_at(index + 1, column(line), "expected a `| ` message line, a comment, a blank line, or `end`")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
content = line.delete_prefix("|")
|
|
110
|
+
content = content.delete_prefix(" ")
|
|
111
|
+
content_column = line.start_with?("| ") ? 3 : 2
|
|
112
|
+
message_lines << decode_line(content, index + 1, content_column)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @rbs (String content, Integer line, Integer content_column) -> String
|
|
116
|
+
def decode_line(content, line, content_column)
|
|
117
|
+
decoded = +""
|
|
118
|
+
index = 0
|
|
119
|
+
while index < content.length
|
|
120
|
+
character = content[index] || raise(Ibex::Error, "missing message character")
|
|
121
|
+
unless character == "\\"
|
|
122
|
+
decoded << character
|
|
123
|
+
index += 1
|
|
124
|
+
next
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
escaped = content[index + 1]
|
|
128
|
+
fail_at(line, content_column + index, "trailing backslash in message line") unless escaped
|
|
129
|
+
replacement = ESCAPES[escaped]
|
|
130
|
+
fail_at(line, content_column + index, "unknown escape \\#{escaped}") unless replacement
|
|
131
|
+
|
|
132
|
+
decoded << replacement
|
|
133
|
+
index += 2
|
|
134
|
+
end
|
|
135
|
+
decoded
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs (String line) -> bool
|
|
139
|
+
def ignorable?(line)
|
|
140
|
+
line.strip.empty? || line.lstrip.start_with?("#")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @rbs () -> String
|
|
144
|
+
def top_level_expectation
|
|
145
|
+
"expected `state N`, `removed N`, a comment, or a blank line"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs (String line) -> Integer
|
|
149
|
+
def column(line)
|
|
150
|
+
(line.index(/\S/) || 0) + 1
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs (Integer line, Integer column, String message) -> bot
|
|
154
|
+
def fail_at(line, column, message)
|
|
155
|
+
raise Ibex::Error, "#{@file}:#{line}:#{column}: #{message}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module ErrorMessages
|
|
5
|
+
# Sentence-keyed ibex-messages v2 parsing, mixed into Parser so both
|
|
6
|
+
# versions share UTF-8, escape, and positioned-diagnostic helpers.
|
|
7
|
+
module ParserV2
|
|
8
|
+
# @rbs!
|
|
9
|
+
# private def ignorable?: (String) -> bool
|
|
10
|
+
# private def column: (String) -> Integer
|
|
11
|
+
# private def fail_at: (Integer, Integer, String) -> bot
|
|
12
|
+
# private def decode_line: (String, Integer, Integer) -> String
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# @rbs () -> Document
|
|
17
|
+
def parse_v2
|
|
18
|
+
entries = [] #: Array[Entry]
|
|
19
|
+
identifiers = {} #: Hash[String, Integer]
|
|
20
|
+
keys = {} #: Hash[String, Integer]
|
|
21
|
+
index = 1
|
|
22
|
+
while index < @lines.length
|
|
23
|
+
line = @lines.fetch(index)
|
|
24
|
+
if ignorable?(line) && !line.lstrip.start_with?("##")
|
|
25
|
+
index += 1
|
|
26
|
+
next
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
entry, index = parse_v2_entry(index)
|
|
30
|
+
record_v2_entry(entry, entries, identifiers, keys)
|
|
31
|
+
end
|
|
32
|
+
Document.new(version: 2, entries: entries)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs (Entry entry, Array[Entry] entries, Hash[String, Integer] identifiers,
|
|
36
|
+
# Hash[String, Integer] keys) -> void
|
|
37
|
+
def record_v2_entry(entry, entries, identifiers, keys)
|
|
38
|
+
error_id = entry.error_id || raise(Ibex::Error, "missing error id")
|
|
39
|
+
reject_v2_duplicate(identifiers, error_id, entry.line, "error id")
|
|
40
|
+
key = entry.sentence ? "#{entry.entry}\0#{entry.sentence.join("\0")}" : "legacy\0#{entry.state}"
|
|
41
|
+
reject_v2_duplicate(keys, key, entry.line, "sentence")
|
|
42
|
+
identifiers[error_id] = entry.line
|
|
43
|
+
keys[key] = entry.line
|
|
44
|
+
entries << entry
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs (Integer opening_index) -> [Entry, Integer]
|
|
48
|
+
def parse_v2_entry(opening_index)
|
|
49
|
+
status, sentence, state = parse_v2_opening(opening_index)
|
|
50
|
+
read_v2_entry_body(opening_index, status, sentence, state)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs (Integer opening_index) ->
|
|
54
|
+
# [:active | :unreachable | :removed, Array[String]?, Integer?]
|
|
55
|
+
def parse_v2_opening(opening_index)
|
|
56
|
+
opening = @lines.fetch(opening_index)
|
|
57
|
+
if (legacy = opening.match(/\Alegacy-state:[ \t]*([0-9]+)\z/))
|
|
58
|
+
return [:removed, nil, Integer(legacy[1] || "0", 10)]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if (sentence = opening.match(/\A(sentence|unreachable):[ \t]*(.*)\z/))
|
|
62
|
+
status = sentence[1] == "unreachable" ? :unreachable : :active #: :unreachable | :active
|
|
63
|
+
return [status, parse_sentence(sentence[2] || "", opening_index + 1), nil]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
fail_at(opening_index + 1, column(opening),
|
|
67
|
+
"expected `sentence: TOKENS`, `unreachable: TOKENS`, or `legacy-state: N`")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @rbs (Integer opening_index, :active | :unreachable | :removed status, Array[String]? sentence,
|
|
71
|
+
# Integer? state) -> [Entry, Integer]
|
|
72
|
+
def read_v2_entry_body(opening_index, status, sentence, state)
|
|
73
|
+
error_id = nil #: String?
|
|
74
|
+
entry_name = nil #: String?
|
|
75
|
+
message_lines = [] #: Array[String]
|
|
76
|
+
index = opening_index + 1
|
|
77
|
+
while index < @lines.length
|
|
78
|
+
line = @lines.fetch(index)
|
|
79
|
+
if line.strip == "end"
|
|
80
|
+
return close_v2_entry(
|
|
81
|
+
opening_index, index, status, sentence, state, error_id, entry_name, message_lines
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
error_id, state, entry_name = read_v2_metadata(
|
|
86
|
+
line, index, error_id, state, entry_name, message_lines
|
|
87
|
+
)
|
|
88
|
+
index += 1
|
|
89
|
+
end
|
|
90
|
+
fail_at(opening_index + 1, 1, "unterminated error-sentence entry")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (Integer opening_index, Integer index, :active | :unreachable | :removed status,
|
|
94
|
+
# Array[String]? sentence, Integer? state, String? error_id, String? entry_name,
|
|
95
|
+
# Array[String] message_lines) -> [Entry, Integer]
|
|
96
|
+
def close_v2_entry(opening_index, index, status, sentence, state, error_id, entry_name, message_lines)
|
|
97
|
+
fail_at(opening_index + 1, 1, "missing `## E0001` error id") unless error_id
|
|
98
|
+
message = message_lines.empty? ? nil : message_lines.join("\n")
|
|
99
|
+
fail_at(opening_index + 1, 1, "message must not be empty") if message&.strip&.empty?
|
|
100
|
+
entry = Entry.new(
|
|
101
|
+
state: state, status: status, message: message, line: opening_index + 1,
|
|
102
|
+
sentence: sentence, error_id: error_id, entry: entry_name
|
|
103
|
+
)
|
|
104
|
+
[entry, index + 1]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @rbs (String line, Integer index, String? error_id, Integer? state, String? entry_name,
|
|
108
|
+
# Array[String] message_lines) -> [String?, Integer?, String?]
|
|
109
|
+
def read_v2_metadata(line, index, error_id, state, entry_name, message_lines)
|
|
110
|
+
stripped = line.strip
|
|
111
|
+
if (match = stripped.match(/\A##[ \t]+(E[0-9]{4,})\z/))
|
|
112
|
+
fail_at(index + 1, column(line), "duplicate error id") if error_id
|
|
113
|
+
error_id = match[1]
|
|
114
|
+
elsif (match = stripped.match(/\A# state:[ \t]*([0-9]+)\z/))
|
|
115
|
+
state = Integer(match[1] || "0", 10)
|
|
116
|
+
elsif (match = stripped.match(/\A# entry:[ \t]*(\S+)\z/))
|
|
117
|
+
entry_name = match[1]
|
|
118
|
+
elsif line.start_with?("|")
|
|
119
|
+
append_v2_message_line(message_lines, line, index)
|
|
120
|
+
elsif !ignorable?(line)
|
|
121
|
+
fail_at(index + 1, column(line), "expected metadata, a `| ` message line, a comment, or `end`")
|
|
122
|
+
end
|
|
123
|
+
[error_id, state, entry_name]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @rbs (Array[String] message_lines, String line, Integer index) -> void
|
|
127
|
+
def append_v2_message_line(message_lines, line, index)
|
|
128
|
+
content = line.delete_prefix("|").delete_prefix(" ")
|
|
129
|
+
content_column = line.start_with?("| ") ? 3 : 2
|
|
130
|
+
message_lines << decode_line(content, index + 1, content_column)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @rbs (String source, Integer line) -> Array[String]
|
|
134
|
+
def parse_sentence(source, line)
|
|
135
|
+
tokens = [] #: Array[String]
|
|
136
|
+
index = 0
|
|
137
|
+
loop do
|
|
138
|
+
index = skip_sentence_space(source, index)
|
|
139
|
+
break if index == source.length
|
|
140
|
+
|
|
141
|
+
token, index = read_sentence_token(source, index, line)
|
|
142
|
+
tokens << token
|
|
143
|
+
end
|
|
144
|
+
fail_at(line, 1, "error sentence must contain at least one token") if tokens.empty?
|
|
145
|
+
tokens
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs (String source, Integer index) -> Integer
|
|
149
|
+
def skip_sentence_space(source, index)
|
|
150
|
+
index += 1 while index < source.length && source[index]&.match?(/\s/)
|
|
151
|
+
index
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @rbs (String source, Integer index, Integer line) -> [String, Integer]
|
|
155
|
+
def read_sentence_token(source, index, line)
|
|
156
|
+
start = index
|
|
157
|
+
quote = source[index]
|
|
158
|
+
if ["'", '"'].include?(quote)
|
|
159
|
+
index = quoted_token_end(source, index, quote || "", line)
|
|
160
|
+
else
|
|
161
|
+
index += 1 while index < source.length && !source[index]&.match?(/\s/)
|
|
162
|
+
end
|
|
163
|
+
token = source[start...index] || raise(Ibex::Error, "missing sentence token")
|
|
164
|
+
[token, index]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @rbs (String source, Integer index, String quote, Integer line) -> Integer
|
|
168
|
+
def quoted_token_end(source, index, quote, line)
|
|
169
|
+
ending = scan_quoted_token(source, index, quote, line)
|
|
170
|
+
return ending if ending == source.length || source[ending]&.match?(/\s/)
|
|
171
|
+
|
|
172
|
+
fail_at(line, ending + 1, "quoted token must be followed by whitespace")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @rbs (String source, Integer index, String quote, Integer line) -> Integer
|
|
176
|
+
def scan_quoted_token(source, index, quote, line)
|
|
177
|
+
index += 1
|
|
178
|
+
while index < source.length
|
|
179
|
+
character = source[index]
|
|
180
|
+
if character == "\\"
|
|
181
|
+
index += 2
|
|
182
|
+
elsif character == quote
|
|
183
|
+
return index + 1
|
|
184
|
+
else
|
|
185
|
+
index += 1
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
fail_at(line, source.length + 1, "unterminated quoted token")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @rbs (Hash[String, Integer] declarations, String key, Integer line, String label) -> void
|
|
192
|
+
def reject_v2_duplicate(declarations, key, line, label)
|
|
193
|
+
previous = declarations[key]
|
|
194
|
+
fail_at(line, 1, "duplicate #{label}; first declared at line #{previous}") if previous
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module ErrorMessages
|
|
5
|
+
# @rbs (IR::Automaton automaton, Array[Entry] entries) -> String
|
|
6
|
+
def render_entries(automaton, entries)
|
|
7
|
+
lines = [
|
|
8
|
+
HEADER,
|
|
9
|
+
"# Generated by `ibex errors --update`; sentences and IDs are stable review keys.",
|
|
10
|
+
"# Edit only `| ` message lines.",
|
|
11
|
+
""
|
|
12
|
+
]
|
|
13
|
+
entries.each { |entry| append_entry(lines, automaton, entry) }
|
|
14
|
+
"#{lines.join("\n").rstrip}\n"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @rbs (Array[String] lines, IR::Automaton automaton, Entry entry) -> void
|
|
18
|
+
def append_entry(lines, automaton, entry)
|
|
19
|
+
lines << entry_heading(entry)
|
|
20
|
+
lines << "## #{entry.error_id}"
|
|
21
|
+
lines << "# entry: #{entry.entry}" if entry.entry
|
|
22
|
+
lines << "# state: #{entry.state}" if entry.state
|
|
23
|
+
append_expected_tokens(lines, automaton, entry)
|
|
24
|
+
lines << "# Add one or more `| ` lines to customize this error." unless entry.message
|
|
25
|
+
entry.message&.split("\n", -1)&.each { |line| lines << "| #{encode_line(line)}" }
|
|
26
|
+
lines.push("end", "")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @rbs (Entry entry) -> String
|
|
30
|
+
def entry_heading(entry)
|
|
31
|
+
return "legacy-state: #{entry.state}" unless entry.sentence
|
|
32
|
+
|
|
33
|
+
prefix = entry.status == :active ? "sentence" : "unreachable"
|
|
34
|
+
"#{prefix}: #{entry.sentence.join(' ')}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs (Array[String] lines, IR::Automaton automaton, Entry entry) -> void
|
|
38
|
+
def append_expected_tokens(lines, automaton, entry)
|
|
39
|
+
return unless entry.status == :active && entry.state
|
|
40
|
+
|
|
41
|
+
expected = expected_tokens(automaton, automaton.states.fetch(entry.state))
|
|
42
|
+
lines << "# expected: #{expected.empty? ? '(none)' : expected.join(', ')}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs (String line) -> String
|
|
46
|
+
def encode_line(line)
|
|
47
|
+
line.gsub("\\", "\\\\").gsub("\t", "\\t").gsub("\r", "\\r")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @rbs (IR::Automaton automaton, IR::AutomatonState state) -> Array[String]
|
|
51
|
+
def expected_tokens(automaton, state)
|
|
52
|
+
ordinary_terminals(automaton).filter_map do |terminal|
|
|
53
|
+
action = state.actions[terminal.id] || state.default_action
|
|
54
|
+
terminal.display_name || terminal.name unless error_action?(action)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module_function :render_entries, :append_entry, :entry_heading, :append_expected_tokens, :encode_line,
|
|
59
|
+
:expected_tokens
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
private :render_entries, :append_entry, :entry_heading, :append_expected_tokens, :encode_line, :expected_tokens
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|