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,75 @@
|
|
|
1
|
+
class Examples::TinyLanguageParser
|
|
2
|
+
pragma extended
|
|
3
|
+
token IDENTIFIER NUMBER PRINT
|
|
4
|
+
%test accept "value=1+2*3; print value;"
|
|
5
|
+
%test accept "value=(8-2)/3;"
|
|
6
|
+
%test reject "print ;"
|
|
7
|
+
rule
|
|
8
|
+
program : statements { result = val[0] }
|
|
9
|
+
statements : statements statement { result = val[0] + [val[1]] }
|
|
10
|
+
| { result = [] }
|
|
11
|
+
statement : IDENTIFIER '=' expression ';' { result = [:assign, val[0], val[2]] }
|
|
12
|
+
| PRINT expression ';' { result = [:print, val[1]] }
|
|
13
|
+
expression : expression '+' term { result = [:add, val[0], val[2]] }
|
|
14
|
+
| expression '-' term { result = [:subtract, val[0], val[2]] }
|
|
15
|
+
| term { result = val[0] }
|
|
16
|
+
term : term '*' factor { result = [:multiply, val[0], val[2]] }
|
|
17
|
+
| term '/' factor { result = [:divide, val[0], val[2]] }
|
|
18
|
+
| factor { result = val[0] }
|
|
19
|
+
factor : NUMBER { result = [:number, val[0]] }
|
|
20
|
+
| IDENTIFIER { result = [:variable, val[0]] }
|
|
21
|
+
| '(' expression ')' { result = val[1] }
|
|
22
|
+
end
|
|
23
|
+
---- header
|
|
24
|
+
require "strscan"
|
|
25
|
+
---- inner
|
|
26
|
+
def parse(source)
|
|
27
|
+
@scanner = StringScanner.new(source)
|
|
28
|
+
do_parse
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def next_token
|
|
32
|
+
@scanner.skip(/\s+/)
|
|
33
|
+
return false if @scanner.eos?
|
|
34
|
+
|
|
35
|
+
if @scanner.scan(/print\b/)
|
|
36
|
+
[:PRINT, "print"]
|
|
37
|
+
elsif (identifier = @scanner.scan(/[A-Za-z_][A-Za-z0-9_]*/))
|
|
38
|
+
[:IDENTIFIER, identifier]
|
|
39
|
+
elsif (number = @scanner.scan(/\d+/))
|
|
40
|
+
[:NUMBER, Integer(number, 10)]
|
|
41
|
+
elsif (punctuation = @scanner.scan(/[=;()+*\/-]/))
|
|
42
|
+
[punctuation, nil]
|
|
43
|
+
else
|
|
44
|
+
raise ArgumentError, "unexpected language input at offset #{@scanner.pos}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def execute(statements, output: $stdout)
|
|
49
|
+
environment = {}
|
|
50
|
+
statements.each do |statement|
|
|
51
|
+
case statement[0]
|
|
52
|
+
when :assign
|
|
53
|
+
environment[statement[1]] = evaluate_expression(statement[2], environment)
|
|
54
|
+
when :print
|
|
55
|
+
output.puts(evaluate_expression(statement[1], environment))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
environment
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def evaluate_expression(expression, environment)
|
|
62
|
+
case expression[0]
|
|
63
|
+
when :number then expression[1]
|
|
64
|
+
when :variable then environment.fetch(expression[1])
|
|
65
|
+
when :add then evaluate_expression(expression[1], environment) + evaluate_expression(expression[2], environment)
|
|
66
|
+
when :subtract then evaluate_expression(expression[1], environment) - evaluate_expression(expression[2], environment)
|
|
67
|
+
when :multiply then evaluate_expression(expression[1], environment) * evaluate_expression(expression[2], environment)
|
|
68
|
+
when :divide then evaluate_expression(expression[1], environment) / evaluate_expression(expression[2], environment)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
---- footer
|
|
72
|
+
if $PROGRAM_NAME == __FILE__
|
|
73
|
+
parser = Examples::TinyLanguageParser.new
|
|
74
|
+
parser.execute(parser.parse(ARGF.read))
|
|
75
|
+
end
|
data/lib/ibex/analysis/sets.rb
CHANGED
|
@@ -104,11 +104,12 @@ module Ibex
|
|
|
104
104
|
|
|
105
105
|
# @rbs () -> void
|
|
106
106
|
def compute_follow
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
@grammar.starts.each do |name|
|
|
108
|
+
start_definition = @grammar.symbol(name)
|
|
109
|
+
raise Ibex::Error, "(analysis):1:1: unknown start symbol #{name}" unless start_definition
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
@follow_bits[start_definition.id] |= bit(0)
|
|
112
|
+
end
|
|
112
113
|
dependencies = Array.new(@grammar.symbols.length) { [] }
|
|
113
114
|
@grammar.productions.each { |production| initialize_follow(production, dependencies) }
|
|
114
115
|
seeds = @grammar.nonterminals.filter_map { |symbol| symbol.id unless @follow_bits[symbol.id].zero? }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
# One fully rendered file in a parser generation.
|
|
6
|
+
class Artifact
|
|
7
|
+
attr_reader :kind #: Symbol
|
|
8
|
+
attr_reader :path #: String
|
|
9
|
+
attr_reader :content #: String
|
|
10
|
+
attr_reader :mode #: Integer?
|
|
11
|
+
|
|
12
|
+
# @rbs (kind: Symbol, path: String, content: String, ?mode: Integer?) -> void
|
|
13
|
+
def initialize(kind:, path:, content:, mode: nil)
|
|
14
|
+
raise ArgumentError, "artifact path must not be empty" if path.empty?
|
|
15
|
+
|
|
16
|
+
@kind = kind
|
|
17
|
+
@path = path.dup.freeze
|
|
18
|
+
@content = content.dup.freeze
|
|
19
|
+
@mode = mode
|
|
20
|
+
freeze
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A generation's rendered outputs before any target is changed.
|
|
25
|
+
class ArtifactSet
|
|
26
|
+
# @rbs! include Enumerable[Artifact]
|
|
27
|
+
# @rbs skip
|
|
28
|
+
include Enumerable
|
|
29
|
+
|
|
30
|
+
# @rbs () -> void
|
|
31
|
+
def initialize
|
|
32
|
+
@artifacts = [] #: Array[Artifact]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs (kind: Symbol, path: String, content: String, ?mode: Integer?) -> Artifact
|
|
36
|
+
def add(kind:, path:, content:, mode: nil)
|
|
37
|
+
artifact = Artifact.new(kind: kind, path: path, content: content, mode: mode)
|
|
38
|
+
@artifacts << artifact
|
|
39
|
+
artifact
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs!
|
|
43
|
+
# def each: () -> Enumerator[Artifact, ArtifactSet]
|
|
44
|
+
# | () { (Artifact) -> void } -> ArtifactSet
|
|
45
|
+
# @rbs skip
|
|
46
|
+
def each(&block)
|
|
47
|
+
return enum_for(:each) unless block
|
|
48
|
+
|
|
49
|
+
@artifacts.each(&block)
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs () -> Array[Artifact]
|
|
54
|
+
def to_a
|
|
55
|
+
@artifacts.dup
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @rbs () -> bool
|
|
59
|
+
def empty?
|
|
60
|
+
@artifacts.empty?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require_relative "../codegen/ambiguity"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
# Bounded grammar ambiguity checking for CI and human inspection.
|
|
9
|
+
module CLIAmbiguity
|
|
10
|
+
# @rbs!
|
|
11
|
+
# private def print_help: (OptionParser) -> Integer
|
|
12
|
+
# private def input_path: (Array[String]) -> String
|
|
13
|
+
# private def positive_counterexample_limit: (Integer, String) -> Integer
|
|
14
|
+
# private def normalize_grammar_path: (String) -> IR::Grammar
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
19
|
+
def run_check_command(arguments)
|
|
20
|
+
parser = ambiguity_option_parser
|
|
21
|
+
remaining = parser.parse(arguments)
|
|
22
|
+
return print_help(parser) if @options[:help]
|
|
23
|
+
raise Ibex::Error, "(cli):1:1: check command requires --ambiguity" unless @options[:check_ambiguity]
|
|
24
|
+
|
|
25
|
+
grammar = normalize_grammar_path(input_path(remaining))
|
|
26
|
+
automaton = LALR::Builder.new(
|
|
27
|
+
grammar, algorithm: @options[:algorithm] || :lalr, entry_isolation: @options[:entry_isolation] == true
|
|
28
|
+
).build
|
|
29
|
+
check = Codegen::Ambiguity.new(
|
|
30
|
+
automaton,
|
|
31
|
+
max_tokens: @options.fetch(:counterexample_max_tokens),
|
|
32
|
+
max_configurations: @options.fetch(:counterexample_max_configurations)
|
|
33
|
+
)
|
|
34
|
+
output = @options[:check_format] == "json" ? JSON.pretty_generate(check.to_h) : check.render_text
|
|
35
|
+
@stdout.puts(output)
|
|
36
|
+
check.exit_status
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @rbs () -> OptionParser
|
|
40
|
+
def ambiguity_option_parser
|
|
41
|
+
OptionParser.new do |options|
|
|
42
|
+
options.banner = "Usage: ibex check --ambiguity [options] grammarfile"
|
|
43
|
+
options.on("--ambiguity", "search parser conflicts for ambiguous sentences") do
|
|
44
|
+
@options[:check_ambiguity] = true
|
|
45
|
+
end
|
|
46
|
+
options.on("--max-tokens=N", Integer, "maximum sentence length (default: 32)") do |value|
|
|
47
|
+
@options[:counterexample_max_tokens] = positive_counterexample_limit(value, "--max-tokens")
|
|
48
|
+
end
|
|
49
|
+
options.on("--max-configurations=N", Integer, "maximum configurations per conflict (default: 50000)") do |value|
|
|
50
|
+
@options[:counterexample_max_configurations] = positive_counterexample_limit(value, "--max-configurations")
|
|
51
|
+
end
|
|
52
|
+
options.on("--format=FORMAT", %w[text json], "text or json (default: text)") do |value|
|
|
53
|
+
@options[:check_format] = value
|
|
54
|
+
end
|
|
55
|
+
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
|
|
56
|
+
@options[:algorithm] = value.to_sym
|
|
57
|
+
end
|
|
58
|
+
options.on("--entry-isolation", "build independent state sets for each start symbol") do
|
|
59
|
+
@options[:entry_isolation] = true
|
|
60
|
+
end
|
|
61
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
|
|
62
|
+
options.on("--help", "show help") { @options[:help] = true }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../lalr/conflict_search_limits"
|
|
4
|
+
|
|
3
5
|
module Ibex
|
|
4
6
|
# Counterexample report limits and their command-line validation.
|
|
5
7
|
module CLICounterexampleOptions
|
|
8
|
+
# @rbs @options: cli_options
|
|
9
|
+
|
|
6
10
|
DEFAULTS = {
|
|
7
|
-
counterexample_max_tokens: LALR::
|
|
8
|
-
counterexample_max_configurations: LALR::
|
|
11
|
+
counterexample_max_tokens: LALR::ConflictSearchLimits::DEFAULT_MAX_TOKENS,
|
|
12
|
+
counterexample_max_configurations: LALR::ConflictSearchLimits::DEFAULT_MAX_CONFIGURATIONS
|
|
9
13
|
}.freeze #: Hash[Symbol, Integer]
|
|
10
14
|
|
|
11
15
|
private
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require_relative "../coverage"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
# CLI collection, merge, and threshold checks for runtime coverage.
|
|
9
|
+
module CLICoverage
|
|
10
|
+
# @rbs!
|
|
11
|
+
# private def same_file_target?: (String left, String right) -> bool
|
|
12
|
+
#
|
|
13
|
+
# type coverage_output_options = {
|
|
14
|
+
# paths: Array[String],
|
|
15
|
+
# ?output: String
|
|
16
|
+
# }
|
|
17
|
+
# type coverage_check_options = {
|
|
18
|
+
# paths: Array[String],
|
|
19
|
+
# min_states: Float,
|
|
20
|
+
# min_productions: Float
|
|
21
|
+
# }
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
26
|
+
def run_coverage_command(arguments)
|
|
27
|
+
operation = arguments.shift
|
|
28
|
+
case operation
|
|
29
|
+
when "collect" then run_coverage_collect(arguments)
|
|
30
|
+
when "merge" then run_coverage_merge(arguments)
|
|
31
|
+
when "check" then run_coverage_check(arguments)
|
|
32
|
+
else
|
|
33
|
+
raise Ibex::Error, "(cli):1:1: coverage requires collect, merge, or check"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
38
|
+
def run_coverage_collect(arguments)
|
|
39
|
+
settings = coverage_output_options(arguments, "collect", "INPUT")
|
|
40
|
+
input = single_coverage_path(settings.fetch(:paths), "coverage collect")
|
|
41
|
+
reject_coverage_output_collision([input], settings[:output])
|
|
42
|
+
report = Coverage::Collector.collect_file(input)
|
|
43
|
+
write_coverage_report(report, settings[:output])
|
|
44
|
+
0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
48
|
+
def run_coverage_merge(arguments)
|
|
49
|
+
settings = coverage_output_options(arguments, "merge", "INPUT...")
|
|
50
|
+
inputs = settings.fetch(:paths)
|
|
51
|
+
raise Ibex::Error, "(cli):1:1: coverage merge requires at least one input" if inputs.empty?
|
|
52
|
+
|
|
53
|
+
reject_coverage_output_collision(inputs, settings[:output])
|
|
54
|
+
reports = inputs.map { |path| Coverage::Report.load_file(path) }
|
|
55
|
+
report = Coverage::Report.merge(reports)
|
|
56
|
+
write_coverage_report(report, settings[:output])
|
|
57
|
+
0
|
|
58
|
+
rescue ArgumentError => e
|
|
59
|
+
raise Ibex::Error, "(coverage):1:1: #{e.message}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
63
|
+
def run_coverage_check(arguments)
|
|
64
|
+
settings = coverage_check_options(arguments)
|
|
65
|
+
path = single_coverage_path(settings.fetch(:paths), "coverage check")
|
|
66
|
+
report = Coverage::Report.load_file(path)
|
|
67
|
+
state_percentage = coverage_percentage(report.state_hits.length, report.state_count)
|
|
68
|
+
production_percentage = coverage_percentage(report.production_hits.length, report.production_count)
|
|
69
|
+
passed = state_percentage >= settings.fetch(:min_states) &&
|
|
70
|
+
production_percentage >= settings.fetch(:min_productions)
|
|
71
|
+
summary = format(
|
|
72
|
+
"coverage %<status>s: states %<states>.2f%% (%<state_hits>d/%<state_total>d), " \
|
|
73
|
+
"productions %<productions>.2f%% (%<production_hits>d/%<production_total>d)",
|
|
74
|
+
status: passed ? "ok" : "failed",
|
|
75
|
+
states: state_percentage,
|
|
76
|
+
state_hits: report.state_hits.length,
|
|
77
|
+
state_total: report.state_count,
|
|
78
|
+
productions: production_percentage,
|
|
79
|
+
production_hits: report.production_hits.length,
|
|
80
|
+
production_total: report.production_count
|
|
81
|
+
)
|
|
82
|
+
(passed ? @stdout : @stderr).puts(summary)
|
|
83
|
+
passed ? 0 : 1
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (Array[String] arguments, String command, String operands) -> coverage_output_options
|
|
87
|
+
def coverage_output_options(arguments, command, operands)
|
|
88
|
+
settings = { paths: [] } #: coverage_output_options
|
|
89
|
+
parser = OptionParser.new do |options|
|
|
90
|
+
options.banner = "Usage: ibex coverage #{command} #{operands} [-o FILE]"
|
|
91
|
+
options.on("-o FILE", "--output=FILE", "write atomically to FILE") { |value| settings[:output] = value }
|
|
92
|
+
end
|
|
93
|
+
settings[:paths] = parser.parse(arguments)
|
|
94
|
+
settings
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @rbs (Array[String] arguments) -> coverage_check_options
|
|
98
|
+
def coverage_check_options(arguments)
|
|
99
|
+
settings = { paths: [], min_states: 0.0, min_productions: 0.0 } #: coverage_check_options
|
|
100
|
+
parser = OptionParser.new do |options|
|
|
101
|
+
options.banner = "Usage: ibex coverage check REPORT [thresholds]"
|
|
102
|
+
options.on("--min-states=PERCENT", Float, "minimum visited states") do |value|
|
|
103
|
+
settings[:min_states] = coverage_threshold(value, "min-states")
|
|
104
|
+
end
|
|
105
|
+
options.on("--min-productions=PERCENT", Float, "minimum reduced productions") do |value|
|
|
106
|
+
settings[:min_productions] = coverage_threshold(value, "min-productions")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
settings[:paths] = parser.parse(arguments)
|
|
110
|
+
settings
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @rbs (Float value, String name) -> Float
|
|
114
|
+
def coverage_threshold(value, name)
|
|
115
|
+
return value if value.finite? && value >= 0.0 && value <= 100.0
|
|
116
|
+
|
|
117
|
+
raise OptionParser::InvalidArgument, "--#{name} must be between 0 and 100"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @rbs (Integer covered, Integer total) -> Float
|
|
121
|
+
def coverage_percentage(covered, total)
|
|
122
|
+
return 100.0 if total.zero?
|
|
123
|
+
|
|
124
|
+
covered.fdiv(total) * 100.0
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @rbs (Array[String] arguments, String command) -> String
|
|
128
|
+
def single_coverage_path(arguments, command)
|
|
129
|
+
return arguments.first if arguments.length == 1
|
|
130
|
+
|
|
131
|
+
raise Ibex::Error, "(cli):1:1: #{command} requires exactly one input"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @rbs (Array[String] inputs, String? output) -> void
|
|
135
|
+
def reject_coverage_output_collision(inputs, output)
|
|
136
|
+
return unless output
|
|
137
|
+
return unless inputs.any? { |input| same_file_target?(input, output) }
|
|
138
|
+
|
|
139
|
+
raise Ibex::Error, "(cli):1:1: coverage input and output paths must be distinct"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @rbs (Coverage::Report report, String? output) -> void
|
|
143
|
+
def write_coverage_report(report, output)
|
|
144
|
+
source = report.to_json
|
|
145
|
+
return @stdout.write(source) unless output
|
|
146
|
+
|
|
147
|
+
atomic_write_coverage(output, source)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @rbs (String path, String source) -> void
|
|
151
|
+
def atomic_write_coverage(path, source)
|
|
152
|
+
target_path = File.symlink?(path) ? File.realpath(path) : path
|
|
153
|
+
directory = File.dirname(File.expand_path(target_path))
|
|
154
|
+
basename = File.basename(target_path)
|
|
155
|
+
Tempfile.create([".#{basename}.", ".tmp"], directory) do |temporary|
|
|
156
|
+
temporary.binmode
|
|
157
|
+
temporary.write(source)
|
|
158
|
+
temporary.flush
|
|
159
|
+
temporary.fsync
|
|
160
|
+
temporary.chmod(coverage_file_mode(target_path))
|
|
161
|
+
temporary.close
|
|
162
|
+
File.rename(temporary.path, target_path)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs (String path) -> Integer
|
|
167
|
+
def coverage_file_mode(path)
|
|
168
|
+
return File.stat(path).mode & 0o777 if File.exist?(path)
|
|
169
|
+
|
|
170
|
+
0o666 & ~File.umask
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require_relative "../table_simulation"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
# Safe parser-table simulation subcommand.
|
|
9
|
+
module CLIDebug
|
|
10
|
+
# @rbs!
|
|
11
|
+
# type debug_options = {
|
|
12
|
+
# paths: Array[String],
|
|
13
|
+
# format: String,
|
|
14
|
+
# max_steps: Integer,
|
|
15
|
+
# max_stack: Integer
|
|
16
|
+
# }
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
21
|
+
def run_debug_command(arguments)
|
|
22
|
+
settings = debug_options(arguments)
|
|
23
|
+
operands = settings.fetch(:paths)
|
|
24
|
+
raise Ibex::Error, "(cli):1:1: debug requires an Automaton IR file" if operands.empty?
|
|
25
|
+
|
|
26
|
+
path = operands.first
|
|
27
|
+
value = IR::Validator.validate(File.read(path))
|
|
28
|
+
raise Ibex::Error, "#{path}:1:1: debug requires Automaton IR" unless value.is_a?(IR::Automaton)
|
|
29
|
+
|
|
30
|
+
simulator = TableSimulation::Simulator.new(
|
|
31
|
+
value,
|
|
32
|
+
max_steps: settings.fetch(:max_steps),
|
|
33
|
+
max_stack: settings.fetch(:max_stack)
|
|
34
|
+
)
|
|
35
|
+
tokens = operands.drop(1)
|
|
36
|
+
interactive = tokens.empty?
|
|
37
|
+
format = settings.fetch(:format)
|
|
38
|
+
result = interactive ? simulate_debug_input(simulator, format) : simulator.simulate(tokens)
|
|
39
|
+
write_debug_result(result, format, steps_written: interactive && format == "text")
|
|
40
|
+
result.status == :accepted ? 0 : 1
|
|
41
|
+
rescue ArgumentError => e
|
|
42
|
+
raise Ibex::Error, "(debug):1:1: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs (Array[String] arguments) -> debug_options
|
|
46
|
+
def debug_options(arguments)
|
|
47
|
+
settings = {
|
|
48
|
+
paths: [],
|
|
49
|
+
format: "text",
|
|
50
|
+
max_steps: TableSimulation::Simulator::DEFAULT_MAX_STEPS,
|
|
51
|
+
max_stack: TableSimulation::Simulator::DEFAULT_MAX_STACK
|
|
52
|
+
} #: debug_options
|
|
53
|
+
parser = OptionParser.new do |options|
|
|
54
|
+
options.banner = "Usage: ibex debug AUTOMATON.json [TOKEN...]"
|
|
55
|
+
options.on("--format=FORMAT", %w[text json], "text or json") { |value| settings[:format] = value }
|
|
56
|
+
options.on("--max-steps=N", Integer, "maximum simulated actions") do |value|
|
|
57
|
+
settings[:max_steps] = positive_debug_budget(value, "max-steps")
|
|
58
|
+
end
|
|
59
|
+
options.on("--max-stack=N", Integer, "maximum simulated state stack depth") do |value|
|
|
60
|
+
settings[:max_stack] = positive_debug_budget(value, "max-stack")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
settings[:paths] = parser.parse(arguments)
|
|
64
|
+
settings
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs (Integer value, String name) -> Integer
|
|
68
|
+
def positive_debug_budget(value, name)
|
|
69
|
+
return value if value.positive?
|
|
70
|
+
|
|
71
|
+
raise OptionParser::InvalidArgument, "--#{name} must be positive"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs (TableSimulation::Simulator simulator, String format) -> TableSimulation::Result
|
|
75
|
+
def simulate_debug_input(simulator, format)
|
|
76
|
+
session = simulator.start
|
|
77
|
+
while (line = @stdin.gets)
|
|
78
|
+
spelling = line.strip
|
|
79
|
+
break if spelling.empty?
|
|
80
|
+
|
|
81
|
+
steps = session.push(spelling)
|
|
82
|
+
write_debug_steps(steps) if format == "text"
|
|
83
|
+
break if session.status
|
|
84
|
+
end
|
|
85
|
+
written = session.steps.length
|
|
86
|
+
result = session.finish
|
|
87
|
+
write_debug_steps(result.steps.drop(written)) if format == "text"
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs (TableSimulation::Result result, String format, steps_written: bool) -> void
|
|
92
|
+
def write_debug_result(result, format, steps_written:)
|
|
93
|
+
if format == "json"
|
|
94
|
+
@stdout.write(result.to_json)
|
|
95
|
+
else
|
|
96
|
+
write_debug_steps(result.steps) unless steps_written
|
|
97
|
+
@stdout.puts("status=#{result.status}")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs (Array[TableSimulation::Step] steps) -> void
|
|
102
|
+
def write_debug_steps(steps)
|
|
103
|
+
steps.each { |step| @stdout.puts(TableSimulation::Text.render_step(step)) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require_relative "../frontend"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
# Batch frontend diagnostics subcommand.
|
|
9
|
+
module CLIDiagnostics
|
|
10
|
+
DIAGNOSTICS_SCHEMA_VERSION = 1 #: Integer
|
|
11
|
+
DEFAULT_MAX_DIAGNOSTICS = 20 #: Integer
|
|
12
|
+
|
|
13
|
+
# @rbs!
|
|
14
|
+
# type diagnostic_settings = {
|
|
15
|
+
# format: String,
|
|
16
|
+
# max_diagnostics: Integer,
|
|
17
|
+
# mode: Symbol,
|
|
18
|
+
# ?help: bool
|
|
19
|
+
# }
|
|
20
|
+
#
|
|
21
|
+
# private def input_path: (Array[String]) -> String
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
26
|
+
def run_diagnose_command(arguments)
|
|
27
|
+
settings = {
|
|
28
|
+
format: "text", max_diagnostics: DEFAULT_MAX_DIAGNOSTICS, mode: :default
|
|
29
|
+
} #: diagnostic_settings
|
|
30
|
+
parser = diagnostics_option_parser(settings)
|
|
31
|
+
remaining = parser.parse(arguments)
|
|
32
|
+
if settings[:help]
|
|
33
|
+
@stdout.puts(parser)
|
|
34
|
+
return 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
path = input_path(remaining)
|
|
38
|
+
result = Frontend::Parser.new(File.binread(path), file: path, mode: settings.fetch(:mode))
|
|
39
|
+
.parse_with_diagnostics(max_diagnostics: settings.fetch(:max_diagnostics))
|
|
40
|
+
result = resolve_diagnostic_result(result, path, settings.fetch(:mode)) if result.success?
|
|
41
|
+
write_diagnostics(result, format: settings.fetch(:format))
|
|
42
|
+
result.success? ? 0 : 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Cross-file recovery is deliberately bounded to the first resolver error.
|
|
46
|
+
# @rbs (Frontend::ParseResult result, String path, Symbol mode) -> Frontend::ParseResult
|
|
47
|
+
def resolve_diagnostic_result(result, path, mode)
|
|
48
|
+
Frontend::Resolver.new(path, mode: mode).resolve
|
|
49
|
+
result
|
|
50
|
+
rescue Frontend::ResolutionIOError
|
|
51
|
+
raise
|
|
52
|
+
rescue Ibex::Error => e
|
|
53
|
+
Frontend::ParseResult.new(diagnostics: [resolution_diagnostic(e, path)], ast: nil, document: nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs (Ibex::Error error, String fallback_file) -> Frontend::Diagnostic
|
|
57
|
+
def resolution_diagnostic(error, fallback_file)
|
|
58
|
+
match = error.message.match(/\A(.+):(\d+):(\d+): (.*)\z/m)
|
|
59
|
+
location = if match
|
|
60
|
+
Frontend::Location.new(
|
|
61
|
+
file: match[1].to_s, line: Integer(match[2].to_s), column: Integer(match[3].to_s)
|
|
62
|
+
)
|
|
63
|
+
else
|
|
64
|
+
Frontend::Location.new(file: fallback_file, line: 1, column: 1)
|
|
65
|
+
end
|
|
66
|
+
message = match ? match[4].to_s : error.message
|
|
67
|
+
Frontend::Diagnostic.new(
|
|
68
|
+
code: "frontend.resolution_error", phase: :syntax, message: message,
|
|
69
|
+
location: location, rendered: error.message
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @rbs (diagnostic_settings settings) -> OptionParser
|
|
74
|
+
def diagnostics_option_parser(settings)
|
|
75
|
+
OptionParser.new do |options|
|
|
76
|
+
options.banner = "Usage: ibex diagnose [options] grammarfile"
|
|
77
|
+
options.on("--format=FORMAT", %w[text json], "text or json") { |value| settings[:format] = value }
|
|
78
|
+
options.on("--max-diagnostics=N", "positive diagnostic limit") do |value|
|
|
79
|
+
settings[:max_diagnostics] = positive_diagnostic_limit(value)
|
|
80
|
+
end
|
|
81
|
+
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| settings[:mode] = value.to_sym }
|
|
82
|
+
options.on("--help", "show help") { settings[:help] = true }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (String value) -> Integer
|
|
87
|
+
def positive_diagnostic_limit(value)
|
|
88
|
+
limit = Integer(value, 10)
|
|
89
|
+
return limit if limit.positive?
|
|
90
|
+
|
|
91
|
+
raise OptionParser::InvalidArgument, "max diagnostics must be positive"
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
raise OptionParser::InvalidArgument, "max diagnostics must be a positive integer"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @rbs (Frontend::ParseResult result, format: String) -> void
|
|
97
|
+
def write_diagnostics(result, format:)
|
|
98
|
+
if format == "json"
|
|
99
|
+
@stdout.puts(JSON.pretty_generate(diagnostics_document(result)))
|
|
100
|
+
else
|
|
101
|
+
result.diagnostics.each { |diagnostic| @stdout.puts(diagnostic.to_s) }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (Frontend::ParseResult result) -> Hash[String, untyped]
|
|
106
|
+
def diagnostics_document(result)
|
|
107
|
+
{
|
|
108
|
+
"ibex_diagnostics" => "frontend",
|
|
109
|
+
"schema_version" => DIAGNOSTICS_SCHEMA_VERSION,
|
|
110
|
+
"success" => result.success?,
|
|
111
|
+
"ast_available" => !result.ast.nil?,
|
|
112
|
+
"diagnostics" => result.diagnostics.map(&:to_h)
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|