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,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Owns open buffers, parsed snapshots, include closures, and reverse dependencies.
|
|
6
|
+
class DocumentStore
|
|
7
|
+
include DocumentStoreValidation
|
|
8
|
+
include DocumentStoreDiagnostics
|
|
9
|
+
|
|
10
|
+
# @rbs!
|
|
11
|
+
# type snapshot = {
|
|
12
|
+
# uri: String,
|
|
13
|
+
# path: String,
|
|
14
|
+
# version: Integer?,
|
|
15
|
+
# source: String,
|
|
16
|
+
# open: bool,
|
|
17
|
+
# document: Frontend::SourceDocument?
|
|
18
|
+
# }
|
|
19
|
+
# type publication = {
|
|
20
|
+
# uri: String,
|
|
21
|
+
# ?version: Integer,
|
|
22
|
+
# diagnostics: Array[WorkspaceAnalyzer::lsp_diagnostic]
|
|
23
|
+
# }
|
|
24
|
+
|
|
25
|
+
attr_reader :loader #: Frontend::SourceLoader
|
|
26
|
+
attr_reader :workspace #: Workspace
|
|
27
|
+
|
|
28
|
+
# @rbs (Workspace workspace, Frontend::SourceLoader loader) -> void
|
|
29
|
+
def initialize(workspace, loader)
|
|
30
|
+
@workspace = workspace
|
|
31
|
+
@loader = loader
|
|
32
|
+
@analyzer = WorkspaceAnalyzer.new(workspace, loader)
|
|
33
|
+
@snapshots = {} #: Hash[String, snapshot]
|
|
34
|
+
@roots = {} #: Hash[String, bool]
|
|
35
|
+
@closures = {} #: Hash[String, Array[String]]
|
|
36
|
+
@reverse_dependencies = {} #: Hash[String, Array[String]]
|
|
37
|
+
@resolutions = {} #: Hash[String, Frontend::Resolution]
|
|
38
|
+
@document_diagnostics = {} #: Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]
|
|
39
|
+
@root_diagnostics = {} #: Hash[String, Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs (String uri, Integer version, String source) -> Array[publication]
|
|
43
|
+
def open(uri, version, source)
|
|
44
|
+
path = workspace.path(uri)
|
|
45
|
+
raise ProtocolError.new("document is already open: #{uri}", code: -32_602) if @snapshots.dig(path, :open)
|
|
46
|
+
|
|
47
|
+
validate_version(version)
|
|
48
|
+
validate_source(source)
|
|
49
|
+
loader.set_overlay(path, source)
|
|
50
|
+
@snapshots[path] = snapshot(uri, path, version, source, true, nil)
|
|
51
|
+
refresh(path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @rbs (String uri, Integer version, String source) -> Array[publication]
|
|
55
|
+
def change(uri, version, source)
|
|
56
|
+
path = workspace.path(uri)
|
|
57
|
+
current = open_snapshot(path, uri)
|
|
58
|
+
validate_version(version)
|
|
59
|
+
unless current.fetch(:version) && version > current.fetch(:version)
|
|
60
|
+
raise ProtocolError.new("document version must increase monotonically", code: -32_602)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
validate_source(source)
|
|
64
|
+
loader.set_overlay(path, source)
|
|
65
|
+
@snapshots[path] = snapshot(uri, path, version, source, true, nil)
|
|
66
|
+
refresh(path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs (String uri, ?source: String?) -> Array[publication]
|
|
70
|
+
def save(uri, source: nil)
|
|
71
|
+
path = workspace.path(uri)
|
|
72
|
+
current = open_snapshot(path, uri)
|
|
73
|
+
if source
|
|
74
|
+
validate_source(source)
|
|
75
|
+
loader.set_overlay(path, source)
|
|
76
|
+
current = snapshot(uri, path, current.fetch(:version), source, true, nil)
|
|
77
|
+
@snapshots[path] = current
|
|
78
|
+
end
|
|
79
|
+
refresh(path)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Clear the open version first, then replace it with a disk snapshot if one exists.
|
|
83
|
+
# @rbs (String uri) -> Array[publication]
|
|
84
|
+
def close(uri)
|
|
85
|
+
path = workspace.path(uri)
|
|
86
|
+
open_snapshot(path, uri)
|
|
87
|
+
clear = { uri: uri, diagnostics: [] } #: publication
|
|
88
|
+
old_files = files_for(path)
|
|
89
|
+
was_root = @roots[path]
|
|
90
|
+
disk_path = disk_path_for_close(path, uri)
|
|
91
|
+
disk_source = loader.disk_source(disk_path) if disk_path && loader.disk_file?(disk_path)
|
|
92
|
+
loader.delete_overlay(path)
|
|
93
|
+
if disk_source
|
|
94
|
+
@snapshots[path] = snapshot(uri, path, nil, disk_source, false, nil)
|
|
95
|
+
if was_root
|
|
96
|
+
analyzed = @analyzer.analyze(path)
|
|
97
|
+
update_snapshot(path, analyzed)
|
|
98
|
+
remove_root_state(path)
|
|
99
|
+
[clear] + publish_paths((old_files + [path]).uniq)
|
|
100
|
+
else
|
|
101
|
+
[clear] + refresh(path)
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
remove_path(path)
|
|
105
|
+
[clear] + refresh_dependents(path)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs (String path) -> snapshot?
|
|
110
|
+
def snapshot_for(path)
|
|
111
|
+
@snapshots[path]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Preserve the URI spelling used to open a document; closed files use their canonical URI.
|
|
115
|
+
# @rbs (String path) -> String
|
|
116
|
+
def uri_for(path)
|
|
117
|
+
entry = @snapshots[path]
|
|
118
|
+
return entry.fetch(:uri) if entry&.fetch(:open)
|
|
119
|
+
|
|
120
|
+
workspace.uri(path)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @rbs (String path) -> Array[String]
|
|
124
|
+
def files_for(path)
|
|
125
|
+
roots = affected_roots(path)
|
|
126
|
+
files = roots.flat_map { |root| @closures.fetch(root, [root]) }
|
|
127
|
+
(files + [path]).uniq
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @rbs (String path) -> Array[Frontend::Resolution]
|
|
131
|
+
def resolutions_for(path)
|
|
132
|
+
affected_roots(path).filter_map { |root| @resolutions[root] }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs (String path) -> Array[String]
|
|
136
|
+
def roots_for(path)
|
|
137
|
+
affected_roots(path)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# @rbs (String path) -> Array[publication]
|
|
143
|
+
def refresh(path)
|
|
144
|
+
old_files = files_for(path)
|
|
145
|
+
previous_roots = affected_roots(path)
|
|
146
|
+
was_root = @roots[path]
|
|
147
|
+
analyzed = @analyzer.analyze(path)
|
|
148
|
+
update_snapshot(path, analyzed)
|
|
149
|
+
update_root_membership(path, analyzed.fetch(:document))
|
|
150
|
+
remove_root_state(path) if was_root && !@roots[path]
|
|
151
|
+
roots = (previous_roots + affected_roots(path)).uniq
|
|
152
|
+
roots.delete(path) unless @roots[path]
|
|
153
|
+
roots.each { |root| refresh_root(root) }
|
|
154
|
+
publish_paths((old_files + files_for(path) + [path]).uniq)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @rbs (String path) -> Array[publication]
|
|
158
|
+
def refresh_dependents(path)
|
|
159
|
+
roots = affected_roots(path)
|
|
160
|
+
roots.each { |root| refresh_root(root) }
|
|
161
|
+
publish_paths(roots.flat_map { |root| @closures.fetch(root, [root]) }.uniq)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @rbs (String root) -> void
|
|
165
|
+
def refresh_root(root)
|
|
166
|
+
files, analyzed = @analyzer.closure(root)
|
|
167
|
+
@closures[root] = files
|
|
168
|
+
rebuild_reverse_dependencies
|
|
169
|
+
analyzed.each { |path, result| update_snapshot(path, result, own_diagnostics: false) }
|
|
170
|
+
parse_failed = analyzed.any? do |path, result|
|
|
171
|
+
!result.fetch(:diagnostics).empty? && loader.file?(path)
|
|
172
|
+
end
|
|
173
|
+
resolution, errors = parse_failed ? [nil, []] : @analyzer.resolve(root)
|
|
174
|
+
resolution ? @resolutions[root] = resolution : @resolutions.delete(root)
|
|
175
|
+
replace_root_diagnostics(root, files, analyzed, errors)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs () -> void
|
|
179
|
+
def rebuild_reverse_dependencies
|
|
180
|
+
reverse = Hash.new { |hash, key| hash[key] = [] } #: Hash[String, Array[String]]
|
|
181
|
+
@closures.each { |root, files| files.each { |file| reverse[file] << root } }
|
|
182
|
+
@reverse_dependencies = reverse
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @rbs (String path) -> Array[String]
|
|
186
|
+
def affected_roots(path)
|
|
187
|
+
roots = @reverse_dependencies.fetch(path, empty_paths).dup
|
|
188
|
+
roots << path if @roots[path]
|
|
189
|
+
roots.uniq
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @rbs (String path, WorkspaceAnalyzer::analyzed_document analyzed, ?own_diagnostics: bool) -> void
|
|
193
|
+
def update_snapshot(path, analyzed, own_diagnostics: true)
|
|
194
|
+
current = @snapshots[path]
|
|
195
|
+
uri = current&.fetch(:uri) || workspace.uri(path)
|
|
196
|
+
version = current&.fetch(:version)
|
|
197
|
+
open = current&.fetch(:open) || false
|
|
198
|
+
source = open ? current.fetch(:source) : analyzed.fetch(:source)
|
|
199
|
+
document = analyzed.fetch(:document)
|
|
200
|
+
@snapshots[path] = snapshot(uri, path, version, source, open, document)
|
|
201
|
+
@document_diagnostics[path] = analyzed.fetch(:diagnostics) if own_diagnostics
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @rbs (String path, Frontend::SourceDocument? document) -> void
|
|
205
|
+
def update_root_membership(path, document)
|
|
206
|
+
if document&.ast.is_a?(Frontend::AST::Root)
|
|
207
|
+
@roots[path] = true
|
|
208
|
+
else
|
|
209
|
+
@roots.delete(path)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# @rbs (Array[String] paths) -> Array[publication]
|
|
214
|
+
def publish_paths(paths)
|
|
215
|
+
paths.filter_map do |path|
|
|
216
|
+
entry = @snapshots[path]
|
|
217
|
+
next unless entry
|
|
218
|
+
|
|
219
|
+
publication = {
|
|
220
|
+
uri: entry.fetch(:uri),
|
|
221
|
+
diagnostics: aggregated_diagnostics(path)
|
|
222
|
+
} #: publication
|
|
223
|
+
publication[:version] = entry.fetch(:version) if entry.fetch(:open) && entry.fetch(:version)
|
|
224
|
+
publication
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# @rbs (String uri, String path, Integer? version, String source, bool open,
|
|
229
|
+
# Frontend::SourceDocument? document) -> snapshot
|
|
230
|
+
def snapshot(uri, path, version, source, open, document)
|
|
231
|
+
{ uri: uri, path: path, version: version, source: source, open: open, document: document }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @rbs (String path, String uri) -> snapshot
|
|
235
|
+
def open_snapshot(path, uri)
|
|
236
|
+
entry = @snapshots[path]
|
|
237
|
+
return entry if entry&.fetch(:open) && entry.fetch(:uri) == uri
|
|
238
|
+
|
|
239
|
+
raise ProtocolError.new("document is not open: #{uri}", code: -32_602)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @rbs () -> Array[String]
|
|
243
|
+
def empty_paths
|
|
244
|
+
[] #: Array[String]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @rbs (String path) -> void
|
|
248
|
+
def remove_path(path)
|
|
249
|
+
@snapshots.delete(path)
|
|
250
|
+
@document_diagnostics.delete(path)
|
|
251
|
+
remove_root_state(path)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @rbs (String path) -> void
|
|
255
|
+
def remove_root_state(path)
|
|
256
|
+
@roots.delete(path)
|
|
257
|
+
@closures.delete(path)
|
|
258
|
+
@resolutions.delete(path)
|
|
259
|
+
@root_diagnostics.delete(path)
|
|
260
|
+
rebuild_reverse_dependencies
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Owns per-document and per-root diagnostic aggregation for DocumentStore.
|
|
6
|
+
module DocumentStoreDiagnostics
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# @rbs (String root, Array[String] files, Hash[String, WorkspaceAnalyzer::analyzed_document] analyzed,
|
|
10
|
+
# Array[WorkspaceAnalyzer::lsp_diagnostic] errors) -> void
|
|
11
|
+
def replace_root_diagnostics(root, files, analyzed, errors)
|
|
12
|
+
# @type self: DocumentStore
|
|
13
|
+
owned = {} #: Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]
|
|
14
|
+
files.each { |path| owned[path] = analyzed.dig(path, :diagnostics) || [] }
|
|
15
|
+
errors.each do |diagnostic|
|
|
16
|
+
file = diagnostic.dig("data", "file")
|
|
17
|
+
next unless file
|
|
18
|
+
|
|
19
|
+
existing = owned[file] || []
|
|
20
|
+
duplicate = existing.any? do |entry|
|
|
21
|
+
entry["range"] == diagnostic["range"] && entry["message"] == diagnostic["message"]
|
|
22
|
+
end
|
|
23
|
+
owned[file] = existing + [diagnostic] unless duplicate
|
|
24
|
+
end
|
|
25
|
+
@root_diagnostics[root] = owned
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs (String path) -> Array[WorkspaceAnalyzer::lsp_diagnostic]
|
|
29
|
+
def aggregated_diagnostics(path)
|
|
30
|
+
# @type self: DocumentStore
|
|
31
|
+
diagnostics = @document_diagnostics.fetch(path, empty_diagnostics).dup
|
|
32
|
+
@root_diagnostics.each_value do |owned|
|
|
33
|
+
diagnostics.concat(owned.fetch(path, empty_diagnostics))
|
|
34
|
+
end
|
|
35
|
+
diagnostics.uniq
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs () -> Array[WorkspaceAnalyzer::lsp_diagnostic]
|
|
39
|
+
def empty_diagnostics
|
|
40
|
+
[] #: Array[WorkspaceAnalyzer::lsp_diagnostic]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Validates versions, source bounds, and prospective multi-file workspace edits.
|
|
6
|
+
module DocumentStoreValidation
|
|
7
|
+
# @rbs (Hash[String, String] replacements) -> bool
|
|
8
|
+
def valid_replacements?(replacements)
|
|
9
|
+
# @type self: DocumentStore
|
|
10
|
+
candidate = Frontend::SourceLoader.new
|
|
11
|
+
@snapshots.each_value do |entry|
|
|
12
|
+
candidate.set_overlay(entry.fetch(:path), entry.fetch(:source)) if entry.fetch(:open)
|
|
13
|
+
end
|
|
14
|
+
replacements.each do |path, source|
|
|
15
|
+
candidate.set_overlay(path, source)
|
|
16
|
+
parse_candidate(path, source)
|
|
17
|
+
end
|
|
18
|
+
roots = replacements.keys.flat_map { |path| affected_roots(path) }.uniq
|
|
19
|
+
roots.each { |root| Frontend::Resolver.new(root, mode: :extended, loader: candidate).resolve }
|
|
20
|
+
true
|
|
21
|
+
rescue Ibex::Error, SystemCallError
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @rbs (Integer version) -> void
|
|
28
|
+
def validate_version(version)
|
|
29
|
+
raise ProtocolError.new("document version must be a non-negative integer", code: -32_602) unless
|
|
30
|
+
version.is_a?(Integer) && !version.negative?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs (String source) -> void
|
|
34
|
+
def validate_source(source)
|
|
35
|
+
raise ProtocolError.new("document text must be a string", code: -32_602) unless source.is_a?(String)
|
|
36
|
+
return if source.bytesize <= Limits::MAX_DOCUMENT_BYTES
|
|
37
|
+
|
|
38
|
+
raise ProtocolError.new("document exceeds #{Limits::MAX_DOCUMENT_BYTES} bytes", code: -32_602)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs (String path, String source) -> void
|
|
42
|
+
def parse_candidate(path, source)
|
|
43
|
+
Frontend::Parser.new(source, file: path, mode: :extended).parse_source_document
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Resolve disk independently from overlay aliases before removing an open buffer.
|
|
47
|
+
# @rbs (String path, String uri) -> String?
|
|
48
|
+
def disk_path_for_close(path, uri)
|
|
49
|
+
# @type self: DocumentStore
|
|
50
|
+
canonical = loader.disk_path(path)
|
|
51
|
+
return canonical if workspace.root_for(canonical)
|
|
52
|
+
|
|
53
|
+
raise ProtocolError.new("document disk target is outside workspace roots: #{uri}", code: -32_602)
|
|
54
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
55
|
+
nil
|
|
56
|
+
rescue SystemCallError => e
|
|
57
|
+
raise ProtocolError.new("cannot resolve document disk target #{uri.inspect}: #{e.message}", code: -32_602)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Handles initialize/initialized/shutdown/exit lifecycle transitions.
|
|
6
|
+
module InitializationHandlers
|
|
7
|
+
include RequestSupport
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# @rbs (untyped raw_params) -> Hash[String, untyped]
|
|
12
|
+
def initialize_workspace(raw_params)
|
|
13
|
+
raise ProtocolError.new("server is already initialized", code: -32_600) unless @state == :uninitialized
|
|
14
|
+
|
|
15
|
+
params = params_hash(raw_params)
|
|
16
|
+
loader = Frontend::SourceLoader.new
|
|
17
|
+
@workspace = Workspace.new(initialization_roots(params), loader)
|
|
18
|
+
@store = DocumentStore.new(@workspace, loader)
|
|
19
|
+
@state = :initialized
|
|
20
|
+
{
|
|
21
|
+
"capabilities" => capabilities,
|
|
22
|
+
"serverInfo" => { "name" => "ibex", "version" => Ibex::VERSION }
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @rbs (untyped params) -> nil
|
|
27
|
+
def initialized_notification(_params)
|
|
28
|
+
require_state!(:initialized)
|
|
29
|
+
@state = :running
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs (untyped params) -> nil
|
|
34
|
+
def shutdown_request(_params)
|
|
35
|
+
require_state!(:running)
|
|
36
|
+
@state = :shutdown
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @rbs (untyped params) -> nil
|
|
41
|
+
def exit_notification(_params)
|
|
42
|
+
@exit_status = @state == :shutdown ? 0 : 1
|
|
43
|
+
@exit_requested = true
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs () -> Hash[String, untyped]
|
|
48
|
+
def capabilities
|
|
49
|
+
{
|
|
50
|
+
"positionEncoding" => "utf-16",
|
|
51
|
+
"textDocumentSync" => { "openClose" => true, "change" => 1, "save" => { "includeText" => true } },
|
|
52
|
+
"definitionProvider" => true,
|
|
53
|
+
"referencesProvider" => true,
|
|
54
|
+
"renameProvider" => { "prepareProvider" => true },
|
|
55
|
+
"hoverProvider" => true,
|
|
56
|
+
"workspace" => { "workspaceFolders" => { "supported" => true, "changeNotifications" => false } }
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs (Hash[String, untyped] params) -> Array[String]
|
|
61
|
+
def initialization_roots(params)
|
|
62
|
+
folders = params["workspaceFolders"]
|
|
63
|
+
if folders.is_a?(Array) && !folders.empty?
|
|
64
|
+
return folders.map do |folder|
|
|
65
|
+
raise ProtocolError.new("workspaceFolders entries must be objects", code: -32_602) unless folder.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
string_member(folder, "uri")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
root = params["rootUri"]
|
|
72
|
+
return [root] if root.is_a?(String)
|
|
73
|
+
|
|
74
|
+
raise ProtocolError.new("initialize requires rootUri or workspaceFolders", code: -32_602)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Serves symbol navigation, rename, and hover requests.
|
|
6
|
+
module NavigationHandlers
|
|
7
|
+
include RequestSupport
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# @rbs (untyped raw_params) -> Array[Hash[String, untyped]]
|
|
12
|
+
def definition(raw_params)
|
|
13
|
+
with_index(raw_params) { |index, path, position| index.definition(path, position) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs (untyped raw_params) -> Array[Hash[String, untyped]]
|
|
17
|
+
def references(raw_params)
|
|
18
|
+
params = params_hash(raw_params)
|
|
19
|
+
context = hash_member(params, "context")
|
|
20
|
+
include_declaration = context["includeDeclaration"] == true
|
|
21
|
+
with_index(params) do |index, path, position|
|
|
22
|
+
index.references(path, position, include_declaration: include_declaration)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @rbs (untyped raw_params) -> Hash[String, untyped]?
|
|
27
|
+
def prepare_rename(raw_params)
|
|
28
|
+
with_index(raw_params) { |index, path, position| index.prepare_rename(path, position) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @rbs (untyped raw_params) -> Hash[String, untyped]
|
|
32
|
+
def rename(raw_params)
|
|
33
|
+
params = params_hash(raw_params)
|
|
34
|
+
new_name = string_member(params, "newName")
|
|
35
|
+
with_index(params) { |index, path, position| index.rename(path, position, new_name) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs (untyped raw_params) -> Hash[String, untyped]?
|
|
39
|
+
def hover(raw_params)
|
|
40
|
+
with_index(raw_params) { |index, path, position| index.hover(path, position) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs (untyped raw_params) { (SymbolIndex, String, Hash[String, untyped]) -> untyped } -> untyped
|
|
44
|
+
def with_index(raw_params)
|
|
45
|
+
require_running!
|
|
46
|
+
params = params_hash(raw_params)
|
|
47
|
+
document = hash_member(params, "textDocument")
|
|
48
|
+
path = workspace.path(string_member(document, "uri"))
|
|
49
|
+
position = hash_member(params, "position")
|
|
50
|
+
yield SymbolIndex.new(store, path), path, position
|
|
51
|
+
rescue ArgumentError => e
|
|
52
|
+
raise ProtocolError.new(e.message, code: -32_602)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Converts between frontend byte offsets and zero-based LSP UTF-16 positions.
|
|
6
|
+
class PositionCodec
|
|
7
|
+
# @rbs (String source) -> void
|
|
8
|
+
def initialize(source)
|
|
9
|
+
@source = Frontend::SourceEncoding.validated_utf8(source, "(lsp)")
|
|
10
|
+
@line_starts = build_line_starts.freeze #: Array[Integer]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @rbs (Integer byte_offset) -> Hash[String, Integer]
|
|
14
|
+
def position(byte_offset)
|
|
15
|
+
validate_byte_offset(byte_offset)
|
|
16
|
+
prefix = @source.byteslice(0, byte_offset) || ""
|
|
17
|
+
raise ArgumentError, "byte offset is not on a UTF-8 character boundary" unless prefix.valid_encoding?
|
|
18
|
+
|
|
19
|
+
line = (@line_starts.bsearch_index { |start| start > byte_offset } || @line_starts.length) - 1
|
|
20
|
+
line_start = @line_starts.fetch(line)
|
|
21
|
+
line_end = content_end(line)
|
|
22
|
+
raise ArgumentError, "byte offset points inside a line ending" if byte_offset > line_end
|
|
23
|
+
|
|
24
|
+
text = @source.byteslice(line_start, byte_offset - line_start) || ""
|
|
25
|
+
{ "line" => line, "character" => utf16_length(text) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs (Hash[String, untyped] position) -> Integer
|
|
29
|
+
def byte_offset(position)
|
|
30
|
+
line = integer_member(position, "line")
|
|
31
|
+
character = integer_member(position, "character")
|
|
32
|
+
raise ArgumentError, "line and character must be non-negative" if line.negative? || character.negative?
|
|
33
|
+
|
|
34
|
+
line_start = @line_starts[line]
|
|
35
|
+
raise ArgumentError, "line is outside the document" unless line_start
|
|
36
|
+
|
|
37
|
+
text = @source.byteslice(line_start, content_end(line) - line_start) || ""
|
|
38
|
+
line_start + byte_length_at_utf16(text, character)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs (Frontend::SourceSpan span) -> Hash[String, Hash[String, Integer]]
|
|
42
|
+
def range(span)
|
|
43
|
+
{ "start" => position(span.start_byte), "end" => position(span.end_byte) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# @rbs () -> Array[Integer]
|
|
49
|
+
def build_line_starts
|
|
50
|
+
starts = [0]
|
|
51
|
+
offset = 0
|
|
52
|
+
@source.each_char do |character|
|
|
53
|
+
offset += character.bytesize
|
|
54
|
+
starts << offset if character == "\n"
|
|
55
|
+
end
|
|
56
|
+
starts
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @rbs (Integer line) -> Integer
|
|
60
|
+
def content_end(line)
|
|
61
|
+
boundary = @line_starts[line + 1] || @source.bytesize
|
|
62
|
+
return boundary unless @line_starts[line + 1]
|
|
63
|
+
|
|
64
|
+
before_newline = boundary - 1
|
|
65
|
+
@source.getbyte(before_newline - 1) == 13 ? before_newline - 1 : before_newline
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @rbs (String text) -> Integer
|
|
69
|
+
def utf16_length(text)
|
|
70
|
+
text.each_codepoint.sum { |codepoint| codepoint > 0xFFFF ? 2 : 1 }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @rbs (String text, Integer units) -> Integer
|
|
74
|
+
def byte_length_at_utf16(text, units)
|
|
75
|
+
consumed_units = 0
|
|
76
|
+
consumed_bytes = 0
|
|
77
|
+
text.each_char do |character|
|
|
78
|
+
return consumed_bytes if consumed_units == units
|
|
79
|
+
|
|
80
|
+
width = character.ord > 0xFFFF ? 2 : 1
|
|
81
|
+
if consumed_units + width > units
|
|
82
|
+
raise ArgumentError, "character points into the middle of a UTF-16 surrogate pair"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
consumed_units += width
|
|
86
|
+
consumed_bytes += character.bytesize
|
|
87
|
+
end
|
|
88
|
+
return consumed_bytes if consumed_units == units
|
|
89
|
+
|
|
90
|
+
raise ArgumentError, "character is outside the document line"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (Hash[String, untyped] position, String name) -> Integer
|
|
94
|
+
def integer_member(position, name)
|
|
95
|
+
value = position[name]
|
|
96
|
+
raise ArgumentError, "#{name} must be an integer" unless value.is_a?(Integer)
|
|
97
|
+
|
|
98
|
+
value
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs (Integer byte_offset) -> void
|
|
102
|
+
def validate_byte_offset(byte_offset)
|
|
103
|
+
return if byte_offset.is_a?(Integer) && byte_offset.between?(0, @source.bytesize)
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, "byte offset is outside the document"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Resource bounds shared by transport and open-document validation.
|
|
6
|
+
module Limits
|
|
7
|
+
MAX_HEADER_BYTES = 16 * 1024
|
|
8
|
+
MAX_MESSAGE_BYTES = 1024 * 1024
|
|
9
|
+
MAX_DOCUMENT_BYTES = 2 * 1024 * 1024
|
|
10
|
+
MAX_OUTPUT_BYTES = 1024 * 1024
|
|
11
|
+
MAX_ERROR_MESSAGE_CHARACTERS = 1024
|
|
12
|
+
MAX_REQUEST_ID_BYTES = 256
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Bounded protocol and workspace failures with their JSON-RPC error code.
|
|
16
|
+
class ProtocolError < Ibex::Error
|
|
17
|
+
attr_reader :code #: Integer
|
|
18
|
+
attr_reader :request_id #: String | Integer | nil
|
|
19
|
+
attr_reader :fatal #: bool
|
|
20
|
+
|
|
21
|
+
# @rbs (String message, ?code: Integer, ?request_id: String | Integer | nil, ?fatal: bool) -> void
|
|
22
|
+
def initialize(message, code: -32_600, request_id: nil, fatal: false)
|
|
23
|
+
normalized = message.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
24
|
+
bounded = normalized[0, Limits::MAX_ERROR_MESSAGE_CHARACTERS] || ""
|
|
25
|
+
bounded += "…" if bounded.length < normalized.length
|
|
26
|
+
super("(lsp):1:1: #{bounded}")
|
|
27
|
+
@code = code
|
|
28
|
+
@request_id = request_id
|
|
29
|
+
@fatal = fatal
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Dispatches the finite LSP surface to focused lifecycle, document, and navigation handlers.
|
|
6
|
+
module RequestHandlers
|
|
7
|
+
include InitializationHandlers
|
|
8
|
+
include DocumentHandlers
|
|
9
|
+
include NavigationHandlers
|
|
10
|
+
include RequestSupport
|
|
11
|
+
|
|
12
|
+
HANDLERS = {
|
|
13
|
+
"initialize" => :initialize_workspace,
|
|
14
|
+
"initialized" => :initialized_notification,
|
|
15
|
+
"shutdown" => :shutdown_request,
|
|
16
|
+
"exit" => :exit_notification,
|
|
17
|
+
"textDocument/didOpen" => :did_open,
|
|
18
|
+
"textDocument/didChange" => :did_change,
|
|
19
|
+
"textDocument/didSave" => :did_save,
|
|
20
|
+
"textDocument/didClose" => :did_close,
|
|
21
|
+
"textDocument/definition" => :definition,
|
|
22
|
+
"textDocument/references" => :references,
|
|
23
|
+
"textDocument/prepareRename" => :prepare_rename,
|
|
24
|
+
"textDocument/rename" => :rename,
|
|
25
|
+
"textDocument/hover" => :hover,
|
|
26
|
+
"$/cancelRequest" => :cancel_request
|
|
27
|
+
}.freeze #: Hash[String, Symbol]
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @rbs (String method, untyped params) -> untyped
|
|
32
|
+
def dispatch(method, params)
|
|
33
|
+
handler = HANDLERS[method]
|
|
34
|
+
raise ProtocolError.new("method not found: #{method}", code: -32_601) unless handler
|
|
35
|
+
|
|
36
|
+
send(handler, params)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @rbs (untyped params) -> nil
|
|
40
|
+
def cancel_request(_params)
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|