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,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
# Canonical target, source, symlink, and lock identity checks for generation.
|
|
6
|
+
module GenerationTransactionValidation
|
|
7
|
+
# @rbs!
|
|
8
|
+
# private def lock_path: (String) -> String
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Follow a final symlink while preserving the symlink path itself during publication.
|
|
13
|
+
# @rbs (String path, ?Hash[String, bool] seen, ?validate_links: bool) -> String
|
|
14
|
+
def resolved_target(path, seen = {}, validate_links: true)
|
|
15
|
+
expanded = File.expand_path(path)
|
|
16
|
+
if File.symlink?(expanded)
|
|
17
|
+
raise Errno::ELOOP, expanded if seen[expanded]
|
|
18
|
+
|
|
19
|
+
seen[expanded] = true
|
|
20
|
+
linked = File.expand_path(File.readlink(expanded), File.dirname(expanded))
|
|
21
|
+
return resolved_target(linked, seen, validate_links: validate_links)
|
|
22
|
+
end
|
|
23
|
+
return existing_target(expanded, validate_links) if File.exist?(expanded)
|
|
24
|
+
|
|
25
|
+
parent = File.realpath(File.dirname(expanded))
|
|
26
|
+
raise Errno::ENOTDIR, parent unless File.directory?(parent)
|
|
27
|
+
|
|
28
|
+
File.join(parent, File.basename(expanded))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @rbs (String expanded, bool validate_links) -> String
|
|
32
|
+
def existing_target(expanded, validate_links)
|
|
33
|
+
target = File.realpath(expanded)
|
|
34
|
+
validate_existing_target!(target) if validate_links
|
|
35
|
+
target
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs (String target) -> void
|
|
39
|
+
def validate_existing_target!(target)
|
|
40
|
+
stat = File.stat(target)
|
|
41
|
+
unless stat.file?
|
|
42
|
+
raise GenerationTransaction::Error,
|
|
43
|
+
"(generation):1:1: output target is not a regular file: #{target}"
|
|
44
|
+
end
|
|
45
|
+
return unless stat.nlink > 1
|
|
46
|
+
|
|
47
|
+
raise GenerationTransaction::Error,
|
|
48
|
+
"(generation):1:1: output target has multiple hard links: #{target}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs () -> void
|
|
52
|
+
def validate_source_boundaries!
|
|
53
|
+
source_paths = @source_records.map(&:path)
|
|
54
|
+
@records.each do |record|
|
|
55
|
+
target = record.fetch(:target)
|
|
56
|
+
collision = source_paths.find { |source| paths_alias?(target, source) }
|
|
57
|
+
next unless collision
|
|
58
|
+
|
|
59
|
+
raise GenerationTransaction::Error,
|
|
60
|
+
"(generation):1:1: artifact target aliases generation input: #{target} (#{collision})"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs (Array[String] lock_paths) -> void
|
|
65
|
+
def validate_lock_boundaries!(lock_paths)
|
|
66
|
+
protected_paths = protected_generation_paths
|
|
67
|
+
lock_paths.each do |path|
|
|
68
|
+
if File.symlink?(path)
|
|
69
|
+
raise GenerationTransaction::Error,
|
|
70
|
+
"(generation):1:1: generation lock must not be a symlink: #{path}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
collision = protected_paths.find { |protected| paths_alias?(path, protected) }
|
|
74
|
+
next unless collision
|
|
75
|
+
|
|
76
|
+
raise GenerationTransaction::Error,
|
|
77
|
+
"(generation):1:1: generation lock aliases a protected path: #{path} (#{collision})"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs (String path, File lock) -> void
|
|
82
|
+
def validate_lock_identity!(path, lock)
|
|
83
|
+
path_stat = File.lstat(path)
|
|
84
|
+
lock_stat = lock.stat
|
|
85
|
+
unless stable_lock_identity?(path_stat, lock_stat)
|
|
86
|
+
raise GenerationTransaction::Error,
|
|
87
|
+
"(generation):1:1: generation lock identity changed while opening: #{path}"
|
|
88
|
+
end
|
|
89
|
+
return unless protected_generation_stats.any? { |stat| same_inode?(stat, lock_stat) }
|
|
90
|
+
|
|
91
|
+
raise GenerationTransaction::Error,
|
|
92
|
+
"(generation):1:1: generation lock aliases a protected inode: #{path}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @rbs () -> Array[String]
|
|
96
|
+
def protected_generation_paths
|
|
97
|
+
@source_records.map(&:path) + @records.map { |record| record.fetch(:target) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @rbs () -> Array[File::Stat]
|
|
101
|
+
def protected_generation_stats
|
|
102
|
+
protected_generation_paths.filter_map { |path| File.stat(path) if File.exist?(path) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (File::Stat path_stat, File::Stat lock_stat) -> bool
|
|
106
|
+
def stable_lock_identity?(path_stat, lock_stat)
|
|
107
|
+
path_stat.file? && path_stat.nlink == 1 && same_inode?(path_stat, lock_stat)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @rbs (File::Stat left, File::Stat right) -> bool
|
|
111
|
+
def same_inode?(left, right)
|
|
112
|
+
left.dev == right.dev && left.ino == right.ino
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @rbs (String left, String right) -> bool
|
|
116
|
+
def paths_alias?(left, right)
|
|
117
|
+
return true if File.expand_path(left) == File.expand_path(right)
|
|
118
|
+
return true if portable_target_key(left) == portable_target_key(right)
|
|
119
|
+
return false unless File.exist?(left) && File.exist?(right)
|
|
120
|
+
|
|
121
|
+
File.identical?(left, right)
|
|
122
|
+
rescue SystemCallError
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Reject target names that collide on common case-insensitive or
|
|
127
|
+
# normalization-insensitive filesystems, even when the current filesystem
|
|
128
|
+
# would permit both names.
|
|
129
|
+
# @rbs (String path) -> [String, String]
|
|
130
|
+
def portable_target_key(path)
|
|
131
|
+
basename = File.basename(path)
|
|
132
|
+
folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
|
|
133
|
+
basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
|
|
134
|
+
else
|
|
135
|
+
basename.downcase
|
|
136
|
+
end
|
|
137
|
+
[File.dirname(path), folded]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @rbs (String phase) -> void
|
|
141
|
+
def ensure_sources_stable!(phase)
|
|
142
|
+
validate_active_locks!(phase)
|
|
143
|
+
validate_current_targets!(phase)
|
|
144
|
+
return if @stability_check.call
|
|
145
|
+
|
|
146
|
+
raise GenerationTransaction::SourceChanged, "(generation):1:1: source changed #{phase}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @rbs (String phase) -> void
|
|
150
|
+
def validate_active_locks!(phase)
|
|
151
|
+
@locked_paths.zip(@locks).each do |path, lock|
|
|
152
|
+
next if stable_lock_identity?(File.lstat(path), lock.stat)
|
|
153
|
+
|
|
154
|
+
raise GenerationTransaction::SourceChanged,
|
|
155
|
+
"(generation):1:1: generation lock changed #{phase}: #{path}"
|
|
156
|
+
end
|
|
157
|
+
rescue SystemCallError => e
|
|
158
|
+
raise GenerationTransaction::SourceChanged,
|
|
159
|
+
"(generation):1:1: cannot revalidate generation lock #{phase}: #{e.message}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @rbs (String phase) -> void
|
|
163
|
+
def validate_current_targets!(phase)
|
|
164
|
+
sources = @source_records.map(&:path)
|
|
165
|
+
@records.each do |record|
|
|
166
|
+
validate_current_target!(record, sources, phase)
|
|
167
|
+
end
|
|
168
|
+
rescue SystemCallError => e
|
|
169
|
+
raise GenerationTransaction::SourceChanged,
|
|
170
|
+
"(generation):1:1: cannot revalidate artifact target #{phase}: #{e.message}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @rbs (Hash[Symbol, untyped] record, Array[String] sources, String phase) -> void
|
|
174
|
+
def validate_current_target!(record, sources, phase)
|
|
175
|
+
artifact = record.fetch(:artifact)
|
|
176
|
+
current = resolved_target(artifact.path, validate_links: false)
|
|
177
|
+
unless current == record.fetch(:target)
|
|
178
|
+
raise GenerationTransaction::SourceChanged,
|
|
179
|
+
"(generation):1:1: artifact target changed #{phase}: #{artifact.path}"
|
|
180
|
+
end
|
|
181
|
+
return unless sources.any? { |source| paths_alias?(current, source) }
|
|
182
|
+
|
|
183
|
+
raise GenerationTransaction::SourceChanged,
|
|
184
|
+
"(generation):1:1: artifact target aliases an input #{phase}: #{artifact.path}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @rbs () -> void
|
|
188
|
+
def validate_locked_targets!
|
|
189
|
+
current = @records.map { |record| lock_path(record.fetch(:target)) }.uniq.sort
|
|
190
|
+
return if current == @locked_paths
|
|
191
|
+
|
|
192
|
+
raise GenerationTransaction::Error,
|
|
193
|
+
"(generation):1:1: output target changed while acquiring generation locks"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "timeout"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
|
|
9
|
+
module Ibex
|
|
10
|
+
# Executes grammar-declared source examples against one generated parser.
|
|
11
|
+
module GrammarTests
|
|
12
|
+
DEFAULT_TIMEOUT = 10
|
|
13
|
+
RESULT_MARKER = "IBEX_GRAMMAR_TEST_RESULT="
|
|
14
|
+
|
|
15
|
+
Result = Struct.new(
|
|
16
|
+
:expectation, #: Symbol
|
|
17
|
+
:actual, #: Symbol
|
|
18
|
+
:error_class, #: String?
|
|
19
|
+
:error_message, #: String?
|
|
20
|
+
:location, #: IR::location
|
|
21
|
+
:production_ids, #: Array[Integer]
|
|
22
|
+
keyword_init: true
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class Result
|
|
26
|
+
# @rbs () -> bool
|
|
27
|
+
def passed? = expectation == actual
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
ProductionCoverage = Struct.new(
|
|
31
|
+
:covered_ids, #: Array[Integer]
|
|
32
|
+
:missing_ids, #: Array[Integer]
|
|
33
|
+
:production_count, #: Integer
|
|
34
|
+
keyword_init: true
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
class ProductionCoverage
|
|
38
|
+
# @rbs () -> Float
|
|
39
|
+
def percentage
|
|
40
|
+
return 100.0 if production_count.zero?
|
|
41
|
+
|
|
42
|
+
covered_ids.length.fdiv(production_count) * 100
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs (Integer minimum) -> bool
|
|
46
|
+
def meets?(minimum)
|
|
47
|
+
covered_ids.length * 100 >= production_count * minimum
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @rbs () -> bool
|
|
51
|
+
def complete? = missing_ids.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Runs all examples in one isolated process while creating a fresh parser
|
|
55
|
+
# instance for every case.
|
|
56
|
+
class Runner
|
|
57
|
+
CHILD_RUNNER = <<~'RUBY'
|
|
58
|
+
require "json"
|
|
59
|
+
|
|
60
|
+
load ARGV.fetch(0)
|
|
61
|
+
parser_class = ARGV.fetch(1).split("::").reject(&:empty?).reduce(Object) do |scope, name|
|
|
62
|
+
scope.const_get(name, false)
|
|
63
|
+
end
|
|
64
|
+
tests = JSON.parse(ARGV.fetch(2))
|
|
65
|
+
results = tests.map do |test|
|
|
66
|
+
production_ids = []
|
|
67
|
+
begin
|
|
68
|
+
parser = parser_class.new
|
|
69
|
+
raise NoMethodError, "#{parser_class} must define parse(source)" unless parser.respond_to?(:parse)
|
|
70
|
+
|
|
71
|
+
parser.observe do |event|
|
|
72
|
+
production_ids << event.data.fetch("production_id") if event.type == :reduce
|
|
73
|
+
end
|
|
74
|
+
parser.parse(test.fetch("source"))
|
|
75
|
+
result = { "actual" => "accept", "error_class" => nil, "error_message" => nil }
|
|
76
|
+
rescue Ibex::Runtime::ParseError => error
|
|
77
|
+
result = { "actual" => "reject", "error_class" => error.class.name, "error_message" => error.message }
|
|
78
|
+
rescue SystemExit, SignalException, StandardError => error
|
|
79
|
+
result = { "actual" => "error", "error_class" => error.class.name, "error_message" => error.message }
|
|
80
|
+
end
|
|
81
|
+
result.merge("production_ids" => production_ids)
|
|
82
|
+
end
|
|
83
|
+
puts "IBEX_GRAMMAR_TEST_RESULT=#{JSON.generate(results)}"
|
|
84
|
+
RUBY
|
|
85
|
+
|
|
86
|
+
# @rbs @automaton: IR::Automaton
|
|
87
|
+
# @rbs @timeout: Integer
|
|
88
|
+
|
|
89
|
+
# @rbs (IR::Automaton automaton, ?timeout: Integer) -> void
|
|
90
|
+
def initialize(automaton, timeout: DEFAULT_TIMEOUT)
|
|
91
|
+
raise ArgumentError, "timeout must be a positive Integer" unless timeout.is_a?(Integer) && timeout.positive?
|
|
92
|
+
|
|
93
|
+
@automaton = automaton
|
|
94
|
+
@timeout = timeout
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @rbs () -> Array[Result]
|
|
98
|
+
def run
|
|
99
|
+
grammar = @automaton.grammar
|
|
100
|
+
raise Ibex::Error, "(test):1:1: grammar declares no %test cases" if grammar.grammar_tests.empty?
|
|
101
|
+
unless grammar.parser_parameters.empty?
|
|
102
|
+
raise Ibex::Error, "(test):1:1: %test cannot instantiate a parser with required %param declarations"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Dir.mktmpdir("ibex-grammar-tests") do |directory|
|
|
106
|
+
parser_path = File.join(directory, "parser.rb")
|
|
107
|
+
runner_path = File.join(directory, "runner.rb")
|
|
108
|
+
File.binwrite(parser_path, generated_parser)
|
|
109
|
+
File.binwrite(runner_path, CHILD_RUNNER)
|
|
110
|
+
execute_child(parser_path, runner_path)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @rbs (Array[Result] results) -> ProductionCoverage
|
|
115
|
+
def production_coverage(results)
|
|
116
|
+
production_count = @automaton.grammar.productions.length
|
|
117
|
+
covered_ids = results.flat_map(&:production_ids).uniq.sort.freeze
|
|
118
|
+
missing_ids = ((0...production_count).to_a - covered_ids).freeze
|
|
119
|
+
ProductionCoverage.new(
|
|
120
|
+
covered_ids: covered_ids, missing_ids: missing_ids, production_count: production_count
|
|
121
|
+
).freeze
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
# @rbs () -> String
|
|
127
|
+
def generated_parser
|
|
128
|
+
Codegen::Ruby.new(@automaton, table: :compact, embedded: true, line_convert: true).generate
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @rbs (String parser_path, String runner_path) -> Array[Result]
|
|
132
|
+
def execute_child(parser_path, runner_path)
|
|
133
|
+
payload = JSON.generate(@automaton.grammar.grammar_tests)
|
|
134
|
+
stdout = Tempfile.new("ibex-grammar-test-stdout")
|
|
135
|
+
stderr = Tempfile.new("ibex-grammar-test-stderr")
|
|
136
|
+
stdout_path = stdout.path || raise(Ibex::Error, "(test):1:1: missing temporary stdout path")
|
|
137
|
+
stderr_path = stderr.path || raise(Ibex::Error, "(test):1:1: missing temporary stderr path")
|
|
138
|
+
pid = spawn(
|
|
139
|
+
RbConfig.ruby, runner_path, parser_path, @automaton.grammar.class_name, payload,
|
|
140
|
+
out: stdout_path, err: stderr_path
|
|
141
|
+
)
|
|
142
|
+
status = wait_for_child(pid)
|
|
143
|
+
output = File.binread(stdout_path)
|
|
144
|
+
errors = File.binread(stderr_path)
|
|
145
|
+
raise_child_failure(status, errors) unless status.success?
|
|
146
|
+
|
|
147
|
+
decode_results(output)
|
|
148
|
+
ensure
|
|
149
|
+
stdout&.close!
|
|
150
|
+
stderr&.close!
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs (Integer pid) -> Process::Status
|
|
154
|
+
def wait_for_child(pid)
|
|
155
|
+
Timeout.timeout(@timeout) { Process.wait2(pid).fetch(1) }
|
|
156
|
+
rescue Timeout::Error
|
|
157
|
+
Process.kill("KILL", pid)
|
|
158
|
+
Process.wait(pid)
|
|
159
|
+
raise Ibex::Error, "(test):1:1: grammar tests exceeded #{@timeout} seconds"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @rbs (Process::Status status, String errors) -> bot
|
|
163
|
+
def raise_child_failure(status, errors)
|
|
164
|
+
detail = errors.lines.last&.strip
|
|
165
|
+
suffix = detail && !detail.empty? ? ": #{detail}" : ""
|
|
166
|
+
raise Ibex::Error, "(test):1:1: grammar test process exited with status #{status.exitstatus}#{suffix}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @rbs (String output) -> Array[Result]
|
|
170
|
+
def decode_results(output)
|
|
171
|
+
line = output.lines.reverse.find { |candidate| candidate.start_with?(RESULT_MARKER) }
|
|
172
|
+
raise Ibex::Error, "(test):1:1: grammar test process produced no result" unless line
|
|
173
|
+
|
|
174
|
+
documents = JSON.parse(line.delete_prefix(RESULT_MARKER).strip)
|
|
175
|
+
tests = @automaton.grammar.grammar_tests
|
|
176
|
+
unless documents.is_a?(Array) && documents.length == tests.length
|
|
177
|
+
raise Ibex::Error, "(test):1:1: grammar test process returned an invalid result count"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
tests.zip(documents).map { |test, document| build_result(test, document) }
|
|
181
|
+
rescue JSON::ParserError, ArgumentError => e
|
|
182
|
+
raise Ibex::Error, "(test):1:1: invalid grammar test process result: #{e.message}"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @rbs (IR::grammar_test test, untyped document) -> Result
|
|
186
|
+
def build_result(test, document)
|
|
187
|
+
unless document.is_a?(Hash) && %w[accept reject error].include?(document["actual"])
|
|
188
|
+
raise Ibex::Error, "(test):1:1: grammar test process returned an invalid case result"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
production_ids = document["production_ids"]
|
|
192
|
+
production_count = @automaton.grammar.productions.length
|
|
193
|
+
unless production_ids.is_a?(Array) &&
|
|
194
|
+
production_ids.all? { |id| id.is_a?(Integer) && id.between?(0, production_count - 1) }
|
|
195
|
+
raise Ibex::Error, "(test):1:1: grammar test process returned invalid production coverage"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
Result.new(
|
|
199
|
+
expectation: test[:expectation], actual: document.fetch("actual").to_sym,
|
|
200
|
+
error_class: document["error_class"], error_message: document["error_message"],
|
|
201
|
+
location: test[:loc], production_ids: production_ids.freeze
|
|
202
|
+
).freeze
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
data/lib/ibex/ir/automaton_ir.rb
CHANGED
|
@@ -74,17 +74,33 @@ module Ibex
|
|
|
74
74
|
attr_reader :grammar_digest #: String
|
|
75
75
|
attr_reader :grammar #: Grammar
|
|
76
76
|
attr_reader :states #: Array[AutomatonState]
|
|
77
|
+
attr_reader :entry_states #: Hash[String, Integer]
|
|
77
78
|
attr_reader :conflict_summary #: conflict_summary
|
|
78
79
|
attr_reader :schema_version #: Integer
|
|
79
80
|
|
|
80
81
|
# @rbs (grammar: Grammar, states: Array[AutomatonState], conflict_summary: conflict_summary,
|
|
81
|
-
# ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer
|
|
82
|
+
# ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer,
|
|
83
|
+
# ?entry_states: Hash[String, Integer]?) -> void
|
|
82
84
|
def initialize(grammar:, states:, conflict_summary:, algorithm: "lalr1", grammar_digest: nil,
|
|
83
|
-
schema_version: SCHEMA_VERSION)
|
|
85
|
+
schema_version: SCHEMA_VERSION, entry_states: nil)
|
|
86
|
+
unless SUPPORTED_SCHEMA_VERSIONS.include?(schema_version)
|
|
87
|
+
raise Ibex::Error, "unsupported automaton schema_version #{schema_version.inspect}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
grammar = Migration.to_v2(grammar) if schema_version >= 2 && grammar.schema_version != schema_version
|
|
91
|
+
raise Ibex::Error, "automaton migration did not produce a grammar" unless grammar.is_a?(Grammar)
|
|
92
|
+
unless grammar.schema_version == schema_version
|
|
93
|
+
raise Ibex::Error,
|
|
94
|
+
"automaton schema_version #{schema_version} requires Grammar IR v#{schema_version}, " \
|
|
95
|
+
"got v#{grammar.schema_version}"
|
|
96
|
+
end
|
|
97
|
+
|
|
84
98
|
@algorithm = algorithm.freeze
|
|
85
99
|
@grammar = grammar
|
|
86
100
|
@grammar_digest = (grammar_digest || digest_for(grammar)).freeze
|
|
87
101
|
@states = states.freeze
|
|
102
|
+
@entry_states = IR.deep_freeze(entry_states || { grammar.start => 0 })
|
|
103
|
+
validate_entry_states
|
|
88
104
|
@conflict_summary = IR.deep_freeze(conflict_summary)
|
|
89
105
|
@schema_version = schema_version
|
|
90
106
|
freeze
|
|
@@ -92,13 +108,30 @@ module Ibex
|
|
|
92
108
|
|
|
93
109
|
# @rbs () -> Hash[Symbol, untyped]
|
|
94
110
|
def to_h
|
|
95
|
-
{ ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
value = { ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
|
|
112
|
+
grammar_digest: @grammar_digest, grammar: @grammar.to_h,
|
|
113
|
+
states: @states.map { |state| state.to_h(@grammar) },
|
|
114
|
+
conflict_summary: @conflict_summary } #: Hash[Symbol, untyped]
|
|
115
|
+
value[:entry_states] = @entry_states unless @entry_states == { @grammar.start => 0 }
|
|
116
|
+
value
|
|
98
117
|
end
|
|
99
118
|
|
|
100
119
|
private
|
|
101
120
|
|
|
121
|
+
# @rbs () -> void
|
|
122
|
+
def validate_entry_states
|
|
123
|
+
ordered = @grammar.starts.select { |name| @entry_states.key?(name) }
|
|
124
|
+
unless @entry_states.any? && @entry_states.keys == ordered
|
|
125
|
+
raise Ibex::Error, "(ir):1:1: automaton entry states must be an ordered subset of grammar starts"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@entry_states.each do |name, state|
|
|
129
|
+
next if state.between?(0, @states.length - 1)
|
|
130
|
+
|
|
131
|
+
raise Ibex::Error, "(ir):1:1: entry #{name} references missing state #{state}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
102
135
|
# @rbs (Grammar grammar) -> String
|
|
103
136
|
def digest_for(grammar)
|
|
104
137
|
require "digest"
|