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,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Builds source occurrences and hover metadata from lossless frontend documents.
|
|
6
|
+
class SymbolIndexBuilder
|
|
7
|
+
include SymbolIndexSourceQueries
|
|
8
|
+
include SymbolIndexPrecedenceReferences
|
|
9
|
+
|
|
10
|
+
# @rbs (DocumentStore store, Array[String] files) -> void
|
|
11
|
+
def initialize(store, files)
|
|
12
|
+
@store = store
|
|
13
|
+
@files = files
|
|
14
|
+
@occurrences = [] #: Array[SymbolOccurrence]
|
|
15
|
+
@documents = {} #: Hash[String, Frontend::SourceDocument]
|
|
16
|
+
@global_kinds = {} #: Hash[String, Symbol]
|
|
17
|
+
@rule_data = {} #: Hash[String, Hash[Symbol, untyped]]
|
|
18
|
+
@terminal_data = Hash.new { |hash, key| hash[key] = {} } #: Hash[String, Hash[Symbol, untyped]]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @rbs () -> [Array[SymbolOccurrence], Hash[String, Frontend::SourceDocument]]
|
|
22
|
+
def build
|
|
23
|
+
collect_documents
|
|
24
|
+
# Definitions from every closure file must exist before references are classified.
|
|
25
|
+
# rubocop:disable Style/CombinableLoops
|
|
26
|
+
@documents.each { |path, document| collect_definitions(path, document) }
|
|
27
|
+
@documents.each { |path, document| collect_references(path, document) }
|
|
28
|
+
# rubocop:enable Style/CombinableLoops
|
|
29
|
+
[@occurrences.freeze, @documents.freeze]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# @rbs () -> void
|
|
35
|
+
def collect_documents
|
|
36
|
+
@files.each do |path|
|
|
37
|
+
snapshot = @store.snapshot_for(path)
|
|
38
|
+
document = snapshot&.fetch(:document)
|
|
39
|
+
@documents[path] = document if document
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs (String path, Frontend::SourceDocument document) -> void
|
|
44
|
+
def collect_definitions(path, document)
|
|
45
|
+
ast = document.ast
|
|
46
|
+
return unless ast
|
|
47
|
+
|
|
48
|
+
declarations = ast.declarations
|
|
49
|
+
declarations.each_with_index do |declaration, index|
|
|
50
|
+
following = declarations[index + 1]&.loc || ast.rules.first&.loc
|
|
51
|
+
collect_declaration(path, document, declaration, following)
|
|
52
|
+
end
|
|
53
|
+
ast.rules.each { |rule| collect_rule_definition(path, document, rule) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration,
|
|
57
|
+
# Frontend::Location? following) -> void
|
|
58
|
+
def collect_declaration(path, document, declaration, following)
|
|
59
|
+
tokens = tokens_between(document, declaration.loc, following)
|
|
60
|
+
case declaration
|
|
61
|
+
when Frontend::AST::Tokens
|
|
62
|
+
add_named_tokens(path, tokens.drop(1), declaration.names, :terminal, :definition)
|
|
63
|
+
when Frontend::AST::DisplayName
|
|
64
|
+
@terminal_data[declaration.name][:display] = declaration.value
|
|
65
|
+
when Frontend::AST::SemanticType
|
|
66
|
+
@terminal_data[declaration.name][:type] = declaration.value
|
|
67
|
+
when Frontend::AST::Precedence
|
|
68
|
+
collect_precedence(declaration)
|
|
69
|
+
when Frontend::AST::Include
|
|
70
|
+
collect_include(path, tokens, declaration)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs (Frontend::AST::Precedence declaration) -> void
|
|
75
|
+
def collect_precedence(declaration)
|
|
76
|
+
declaration.levels.each_with_index do |level, index|
|
|
77
|
+
level.symbols.each do |name|
|
|
78
|
+
@terminal_data[name][:precedence] = {
|
|
79
|
+
direction: declaration.direction, associativity: level.associativity, level: index
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs (String path, Array[Frontend::Token] tokens, Frontend::AST::Include declaration) -> void
|
|
86
|
+
def collect_include(path, tokens, declaration)
|
|
87
|
+
token = tokens.find { |candidate| candidate.type == :literal }
|
|
88
|
+
return unless token&.span
|
|
89
|
+
|
|
90
|
+
target = canonical_include_target(declaration)
|
|
91
|
+
return unless target
|
|
92
|
+
|
|
93
|
+
add_occurrence(
|
|
94
|
+
name: declaration.path, kind: :include, role: :reference, key: [:include, target],
|
|
95
|
+
path: path, span: token.span, data: { target: target }
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule) -> void
|
|
100
|
+
def collect_rule_definition(path, document, rule)
|
|
101
|
+
token = token_at(document, rule.loc, rule.lhs)
|
|
102
|
+
return unless token&.span
|
|
103
|
+
|
|
104
|
+
@global_kinds[rule.lhs] ||= :rule
|
|
105
|
+
signature = "#{rule.lhs}#{parameter_signature(rule.parameters)}"
|
|
106
|
+
signature = "%inline #{signature}" if rule.inline
|
|
107
|
+
data = {
|
|
108
|
+
signature: signature,
|
|
109
|
+
documentation: rule.documentation,
|
|
110
|
+
inline: rule.inline,
|
|
111
|
+
parameters: rule.parameters
|
|
112
|
+
}
|
|
113
|
+
@rule_data[rule.lhs] ||= data
|
|
114
|
+
add_occurrence(
|
|
115
|
+
name: rule.lhs, kind: :rule, role: :definition, key: global_key(rule.lhs),
|
|
116
|
+
path: path, span: token.span, data: data
|
|
117
|
+
)
|
|
118
|
+
collect_parameters(path, document, rule, token)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule,
|
|
122
|
+
# Frontend::Token lhs_token) -> void
|
|
123
|
+
def collect_parameters(path, document, rule, lhs_token)
|
|
124
|
+
return if rule.parameters.empty?
|
|
125
|
+
|
|
126
|
+
rule_id = lhs_token.span&.start_byte || 0
|
|
127
|
+
rule.parameters.zip(parameter_tokens(document, lhs_token)).each do |parameter, token|
|
|
128
|
+
next unless token&.span
|
|
129
|
+
|
|
130
|
+
add_occurrence(
|
|
131
|
+
name: parameter, kind: :parameter, role: :definition,
|
|
132
|
+
key: parameter_key(path, rule_id, parameter), path: path, span: token.span,
|
|
133
|
+
data: { rule: rule.lhs }
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs (String path, Frontend::SourceDocument document) -> void
|
|
139
|
+
def collect_references(path, document)
|
|
140
|
+
ast = document.ast
|
|
141
|
+
return unless ast
|
|
142
|
+
|
|
143
|
+
ast.declarations.each_with_index do |declaration, index|
|
|
144
|
+
following = ast.declarations[index + 1]&.loc || ast.rules.first&.loc
|
|
145
|
+
collect_declaration_references(path, document, declaration, following)
|
|
146
|
+
end
|
|
147
|
+
ast.rules.each_with_index do |rule, index|
|
|
148
|
+
collect_rule_references(path, document, rule, ast.rules[index + 1]&.loc)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration,
|
|
153
|
+
# Frontend::Location? following) -> void
|
|
154
|
+
def collect_declaration_references(path, document, declaration, following)
|
|
155
|
+
names = declaration_reference_names(declaration)
|
|
156
|
+
return if names.empty?
|
|
157
|
+
|
|
158
|
+
remaining = tokens_between(document, declaration.loc, following).drop(1)
|
|
159
|
+
names.each do |name|
|
|
160
|
+
index = remaining.index { |token| token.value == name }
|
|
161
|
+
token = index && remaining.delete_at(index)
|
|
162
|
+
add_global_reference(path, token, name) if token
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule,
|
|
167
|
+
# Frontend::Location? following_rule) -> void
|
|
168
|
+
def collect_rule_references(path, document, rule, following_rule)
|
|
169
|
+
definition = token_at(document, rule.loc, rule.lhs)
|
|
170
|
+
rule_id = definition&.span&.start_byte || 0
|
|
171
|
+
rule.alternatives.each_with_index do |alternative, index|
|
|
172
|
+
alternative.items.each { |item| collect_item(path, document, item, rule, rule_id) }
|
|
173
|
+
following = rule.alternatives[index + 1]&.loc || following_rule
|
|
174
|
+
collect_alternative_precedence(path, document, alternative, following, rule, rule_id)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::item item,
|
|
179
|
+
# Frontend::AST::Rule rule, Integer rule_id) -> void
|
|
180
|
+
def collect_item(path, document, item, rule, rule_id)
|
|
181
|
+
case item
|
|
182
|
+
when Frontend::AST::SymbolReference, Frontend::AST::ParameterizedReference
|
|
183
|
+
collect_named_item(path, document, item, rule, rule_id)
|
|
184
|
+
item.arguments.each { |child| collect_item(path, document, child, rule, rule_id) } if
|
|
185
|
+
item.is_a?(Frontend::AST::ParameterizedReference)
|
|
186
|
+
when Frontend::AST::Optional, Frontend::AST::Star, Frontend::AST::Plus
|
|
187
|
+
collect_item(path, document, item.item, rule, rule_id)
|
|
188
|
+
when Frontend::AST::Group
|
|
189
|
+
item.alternatives.flatten.each { |child| collect_item(path, document, child, rule, rule_id) }
|
|
190
|
+
when Frontend::AST::SeparatedList
|
|
191
|
+
collect_item(path, document, item.item, rule, rule_id)
|
|
192
|
+
collect_item(path, document, item.separator, rule, rule_id)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @rbs (String path, Frontend::SourceDocument document,
|
|
197
|
+
# Frontend::AST::SymbolReference | Frontend::AST::ParameterizedReference item,
|
|
198
|
+
# Frontend::AST::Rule rule, Integer rule_id) -> void
|
|
199
|
+
def collect_named_item(path, document, item, rule, rule_id)
|
|
200
|
+
token = token_at(document, item.loc, item.name)
|
|
201
|
+
return unless token&.span
|
|
202
|
+
|
|
203
|
+
add_rule_reference(path, token, item.name, rule, rule_id)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @rbs (String path, Frontend::Token token, String name) -> void
|
|
207
|
+
def add_global_reference(path, token, name)
|
|
208
|
+
return unless token.span
|
|
209
|
+
|
|
210
|
+
kind = @global_kinds[name] || :terminal
|
|
211
|
+
data = symbol_data(kind, name)
|
|
212
|
+
add_occurrence(name: name, kind: kind, role: :reference, key: global_key(name),
|
|
213
|
+
path: path, span: token.span, data: data)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# @rbs (Symbol kind, String name) -> Hash[Symbol, untyped]
|
|
217
|
+
def symbol_data(kind, name)
|
|
218
|
+
data = kind == :rule ? @rule_data[name] : @terminal_data[name]
|
|
219
|
+
empty = {} #: Hash[Symbol, untyped]
|
|
220
|
+
data || empty
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @rbs (String path, Array[Frontend::Token] tokens, Array[String] names, Symbol kind, Symbol role) -> void
|
|
224
|
+
def add_named_tokens(path, tokens, names, kind, role)
|
|
225
|
+
remaining = tokens.dup
|
|
226
|
+
names.each do |name|
|
|
227
|
+
index = remaining.index { |token| token.value == name }
|
|
228
|
+
token = index && remaining.delete_at(index)
|
|
229
|
+
next unless token&.span
|
|
230
|
+
|
|
231
|
+
@global_kinds[name] ||= kind
|
|
232
|
+
add_occurrence(name: name, kind: kind, role: role, key: global_key(name),
|
|
233
|
+
path: path, span: token.span, data: @terminal_data[name])
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# @rbs (**untyped attributes) -> void
|
|
238
|
+
def add_occurrence(**attributes)
|
|
239
|
+
@occurrences << SymbolOccurrence.new(**attributes)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @rbs (String name) -> Array[untyped]
|
|
243
|
+
def global_key(name)
|
|
244
|
+
[:symbol, name]
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @rbs (String path, Integer rule_id, String name) -> Array[untyped]
|
|
248
|
+
def parameter_key(path, rule_id, name)
|
|
249
|
+
[:parameter, path, rule_id, name]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @rbs (Array[String] parameters) -> String
|
|
253
|
+
def parameter_signature(parameters)
|
|
254
|
+
parameters.empty? ? "" : "(#{parameters.join(', ')})"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @rbs (Frontend::AST::Include declaration) -> String?
|
|
258
|
+
def canonical_include_target(declaration)
|
|
259
|
+
candidate = File.expand_path(declaration.path, File.dirname(declaration.loc.file))
|
|
260
|
+
target = @store.loader.canonical_path(candidate, allow_missing: true)
|
|
261
|
+
@store.workspace.root_for(target) && target
|
|
262
|
+
rescue SystemCallError
|
|
263
|
+
nil
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Indexes rule-scoped item and alternative precedence references.
|
|
6
|
+
module SymbolIndexPrecedenceReferences
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# @rbs (String path, Frontend::SourceDocument document, Frontend::AST::Alternative alternative,
|
|
10
|
+
# Frontend::Location? following, Frontend::AST::Rule rule, Integer rule_id) -> void
|
|
11
|
+
def collect_alternative_precedence(path, document, alternative, following, rule, rule_id)
|
|
12
|
+
# @type self: SymbolIndexBuilder
|
|
13
|
+
name = alternative.precedence
|
|
14
|
+
return unless name
|
|
15
|
+
|
|
16
|
+
tokens = tokens_between(document, alternative.loc, following)
|
|
17
|
+
index = (0...(tokens.length - 1)).to_a.reverse.find do |candidate|
|
|
18
|
+
tokens.fetch(candidate).value == "=" && tokens.fetch(candidate + 1).value == name
|
|
19
|
+
end
|
|
20
|
+
token = index && tokens[index + 1]
|
|
21
|
+
add_rule_reference(path, token, name, rule, rule_id) if token
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @rbs (String path, Frontend::Token token, String name, Frontend::AST::Rule rule,
|
|
25
|
+
# Integer rule_id) -> void
|
|
26
|
+
def add_rule_reference(path, token, name, rule, rule_id)
|
|
27
|
+
# @type self: SymbolIndexBuilder
|
|
28
|
+
return unless token.span
|
|
29
|
+
|
|
30
|
+
if rule.parameters.include?(name)
|
|
31
|
+
key = parameter_key(path, rule_id, name)
|
|
32
|
+
kind = :parameter
|
|
33
|
+
data = { rule: rule.lhs }
|
|
34
|
+
else
|
|
35
|
+
key = global_key(name)
|
|
36
|
+
kind = @global_kinds[name] || :rule
|
|
37
|
+
data = symbol_data(kind, name)
|
|
38
|
+
end
|
|
39
|
+
add_occurrence(name: name, kind: kind, role: :reference, key: key,
|
|
40
|
+
path: path, span: token.span, data: data)
|
|
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
|
+
# Locates lossless frontend tokens used by the symbol index.
|
|
6
|
+
module SymbolIndexSourceQueries
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# @rbs (Frontend::SourceDocument document, Frontend::Location start,
|
|
10
|
+
# Frontend::Location? finish) -> Array[Frontend::Token]
|
|
11
|
+
def tokens_between(document, start, finish)
|
|
12
|
+
document.tokens.select do |token|
|
|
13
|
+
after = !location_before?(token.location, start)
|
|
14
|
+
before = finish.nil? || location_before?(token.location, finish)
|
|
15
|
+
after && before
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs (Frontend::SourceDocument document, Frontend::Location location, String value) -> Frontend::Token?
|
|
20
|
+
def token_at(document, location, value)
|
|
21
|
+
document.tokens.find do |token|
|
|
22
|
+
token.location.line == location.line && token.location.column == location.column && token.value == value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @rbs (Frontend::SourceDocument document, Frontend::Token lhs_token) -> Array[Frontend::Token]
|
|
27
|
+
def parameter_tokens(document, lhs_token)
|
|
28
|
+
start_index = document.tokens.index(lhs_token)
|
|
29
|
+
return [] unless start_index
|
|
30
|
+
|
|
31
|
+
tail = document.tokens.drop(start_index + 1)
|
|
32
|
+
return [] unless tail.first&.type == :"("
|
|
33
|
+
|
|
34
|
+
tail.take_while { |token| token.type != :")" }.select { |token| token.type == :identifier }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs (Frontend::AST::declaration declaration) -> Array[String]
|
|
38
|
+
def declaration_reference_names(declaration)
|
|
39
|
+
case declaration
|
|
40
|
+
when Frontend::AST::Start, Frontend::AST::OnErrorReduce
|
|
41
|
+
declaration.names
|
|
42
|
+
when Frontend::AST::Recovery
|
|
43
|
+
declaration.sync_tokens
|
|
44
|
+
when Frontend::AST::DisplayName, Frontend::AST::SemanticType
|
|
45
|
+
[declaration.name]
|
|
46
|
+
when Frontend::AST::Convert
|
|
47
|
+
declaration.pairs.map(&:name)
|
|
48
|
+
when Frontend::AST::Precedence
|
|
49
|
+
declaration.levels.flat_map(&:symbols)
|
|
50
|
+
else
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs (Frontend::Location left, Frontend::Location right) -> bool
|
|
56
|
+
def location_before?(left, right)
|
|
57
|
+
left.line < right.line || (left.line == right.line && left.column < right.column)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# A source-backed grammar symbol definition or reference.
|
|
6
|
+
SymbolOccurrence = Struct.new(
|
|
7
|
+
:name, #: String
|
|
8
|
+
:kind, #: Symbol
|
|
9
|
+
:role, #: Symbol
|
|
10
|
+
:key, #: Array[untyped]
|
|
11
|
+
:path, #: String
|
|
12
|
+
:span, #: Frontend::SourceSpan
|
|
13
|
+
:data, #: Hash[Symbol, untyped]
|
|
14
|
+
keyword_init: true
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Reads and writes bounded LSP JSON-RPC messages over Content-Length framing.
|
|
6
|
+
class Transport
|
|
7
|
+
HEADER_SEPARATOR = "\r\n\r\n"
|
|
8
|
+
|
|
9
|
+
# @rbs (untyped input, untyped output) -> void
|
|
10
|
+
def initialize(input, output)
|
|
11
|
+
@input = input
|
|
12
|
+
@output = output
|
|
13
|
+
@buffer = String.new(encoding: Encoding::BINARY)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs () -> Hash[String, untyped]?
|
|
17
|
+
def read_message
|
|
18
|
+
header = read_header
|
|
19
|
+
return unless header
|
|
20
|
+
|
|
21
|
+
length = content_length(header)
|
|
22
|
+
body = read_bytes(length)
|
|
23
|
+
parsed = JSON.parse(body)
|
|
24
|
+
raise ProtocolError.new("JSON-RPC message must be an object", code: -32_600) unless parsed.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
parsed
|
|
27
|
+
rescue JSON::ParserError => e
|
|
28
|
+
raise ProtocolError.new("malformed JSON: #{e.message}", code: -32_700)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @rbs (Hash[String, untyped] message) -> void
|
|
32
|
+
def write_message(message)
|
|
33
|
+
body = JSON.generate(message)
|
|
34
|
+
if body.bytesize > Limits::MAX_OUTPUT_BYTES
|
|
35
|
+
raise ProtocolError.new("JSON-RPC output exceeds #{Limits::MAX_OUTPUT_BYTES} bytes", code: -32_603)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@output.write("Content-Length: #{body.bytesize}\r\n\r\n")
|
|
39
|
+
@output.write(body)
|
|
40
|
+
@output.flush if @output.respond_to?(:flush)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# @rbs () -> String?
|
|
46
|
+
def read_header
|
|
47
|
+
loop do
|
|
48
|
+
index = @buffer.index(HEADER_SEPARATOR)
|
|
49
|
+
if index
|
|
50
|
+
if index > Limits::MAX_HEADER_BYTES
|
|
51
|
+
raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes",
|
|
52
|
+
code: -32_700, fatal: true)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
header = @buffer.byteslice(0, index) || ""
|
|
56
|
+
@buffer = @buffer.byteslice(index + HEADER_SEPARATOR.bytesize, @buffer.bytesize) || +""
|
|
57
|
+
return header
|
|
58
|
+
end
|
|
59
|
+
if @buffer.bytesize > Limits::MAX_HEADER_BYTES
|
|
60
|
+
raise ProtocolError.new("JSON-RPC header exceeds #{Limits::MAX_HEADER_BYTES} bytes",
|
|
61
|
+
code: -32_700, fatal: true)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
chunk = read_chunk(4096)
|
|
65
|
+
return nil if chunk.nil? && @buffer.empty?
|
|
66
|
+
raise ProtocolError.new("truncated JSON-RPC header", code: -32_700, fatal: true) unless chunk
|
|
67
|
+
|
|
68
|
+
@buffer << chunk.b
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs (String header) -> Integer
|
|
73
|
+
def content_length(header)
|
|
74
|
+
values = header.split("\r\n").filter_map do |line|
|
|
75
|
+
name, value = line.split(":", 2)
|
|
76
|
+
raise ProtocolError.new("malformed JSON-RPC header", code: -32_700, fatal: true) unless value
|
|
77
|
+
|
|
78
|
+
value.strip if name&.casecmp?("Content-Length")
|
|
79
|
+
end
|
|
80
|
+
unless values.one? && values.fetch(0).match?(/\A\d+\z/)
|
|
81
|
+
raise ProtocolError.new("exactly one numeric Content-Length header is required",
|
|
82
|
+
code: -32_700, fatal: true)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
length = Integer(values.fetch(0), 10)
|
|
86
|
+
if length > Limits::MAX_MESSAGE_BYTES
|
|
87
|
+
raise ProtocolError.new("JSON-RPC message exceeds #{Limits::MAX_MESSAGE_BYTES} bytes",
|
|
88
|
+
code: -32_700, fatal: true)
|
|
89
|
+
end
|
|
90
|
+
length
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (Integer length) -> String
|
|
94
|
+
def read_bytes(length)
|
|
95
|
+
while @buffer.bytesize < length
|
|
96
|
+
chunk = read_chunk([4096, length - @buffer.bytesize].min)
|
|
97
|
+
raise ProtocolError.new("truncated JSON-RPC body", code: -32_700, fatal: true) unless chunk
|
|
98
|
+
|
|
99
|
+
@buffer << chunk.b
|
|
100
|
+
end
|
|
101
|
+
body = @buffer.byteslice(0, length) || ""
|
|
102
|
+
@buffer = @buffer.byteslice(length, @buffer.bytesize) || +""
|
|
103
|
+
body.force_encoding(Encoding::UTF_8)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# IO#read(length) may wait for the entire length on a live pipe. readpartial
|
|
107
|
+
# lets the stdio server process the bytes that are already available.
|
|
108
|
+
# @rbs (Integer length) -> String?
|
|
109
|
+
def read_chunk(length)
|
|
110
|
+
return @input.read(length) unless @input.respond_to?(:readpartial)
|
|
111
|
+
|
|
112
|
+
@input.readpartial(length)
|
|
113
|
+
rescue EOFError
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module LSP
|
|
5
|
+
# Converts file URIs to canonical paths constrained to initialized workspace roots.
|
|
6
|
+
class Workspace
|
|
7
|
+
attr_reader :roots #: Array[String]
|
|
8
|
+
|
|
9
|
+
# @rbs (Array[String] root_uris, Frontend::SourceLoader loader) -> void
|
|
10
|
+
def initialize(root_uris, loader)
|
|
11
|
+
raise ProtocolError.new("initialize requires a workspace root", code: -32_602) if root_uris.empty?
|
|
12
|
+
|
|
13
|
+
@loader = loader
|
|
14
|
+
@roots = root_uris.map { |uri| canonical_root(uri) }.uniq.sort.freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @rbs (String uri, ?allow_missing: bool) -> String
|
|
18
|
+
def path(uri, allow_missing: true)
|
|
19
|
+
parsed = parse_file_uri(uri)
|
|
20
|
+
decoded = decode_path(parsed.path)
|
|
21
|
+
|
|
22
|
+
canonical = @loader.canonical_path(decoded, allow_missing: allow_missing)
|
|
23
|
+
return canonical if root_for(canonical)
|
|
24
|
+
|
|
25
|
+
raise ProtocolError.new("document is outside initialized workspace roots: #{uri}", code: -32_602)
|
|
26
|
+
rescue URI::InvalidURIError, ArgumentError => e
|
|
27
|
+
raise ProtocolError.new("invalid file URI #{uri.inspect}: #{e.message}", code: -32_602)
|
|
28
|
+
rescue SystemCallError => e
|
|
29
|
+
raise ProtocolError.new("cannot resolve file URI #{uri.inspect}: #{e.message}", code: -32_602)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @rbs (String path) -> String
|
|
33
|
+
def uri(path)
|
|
34
|
+
canonical = @loader.canonical_path(path, allow_missing: true)
|
|
35
|
+
parser = begin
|
|
36
|
+
URI::RFC2396_PARSER
|
|
37
|
+
rescue NameError
|
|
38
|
+
URI::DEFAULT_PARSER
|
|
39
|
+
end
|
|
40
|
+
"file://#{parser.escape(canonical)}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs (String path) -> String?
|
|
44
|
+
def root_for(path)
|
|
45
|
+
roots.select { |root| inside?(path, root) }.max_by(&:length)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# URI is a stdlib boundary whose implementation class varies by scheme.
|
|
51
|
+
# @rbs (String uri) -> untyped
|
|
52
|
+
def parse_file_uri(uri)
|
|
53
|
+
validate_raw_authority!(uri)
|
|
54
|
+
parsed = URI.parse(uri)
|
|
55
|
+
path = parsed.path
|
|
56
|
+
local_host = parsed.host.nil? || parsed.host.empty? || parsed.host.casecmp?("localhost")
|
|
57
|
+
unless parsed.scheme == "file" && local_host &&
|
|
58
|
+
parsed.query.nil? && parsed.fragment.nil? && path&.start_with?("/")
|
|
59
|
+
raise ArgumentError, "only absolute local file URIs are supported"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
parsed
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Ruby's URI::File may discard userinfo and port, so validate the raw authority first.
|
|
66
|
+
# @rbs (String uri) -> void
|
|
67
|
+
def validate_raw_authority!(uri)
|
|
68
|
+
match = uri.match(%r{\Afile://([^/]*)}i)
|
|
69
|
+
return unless match
|
|
70
|
+
|
|
71
|
+
authority = match[1] || ""
|
|
72
|
+
return if authority.empty? || authority.casecmp?("localhost")
|
|
73
|
+
|
|
74
|
+
raise ArgumentError, "only an empty authority or localhost is supported"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs (String uri) -> String
|
|
78
|
+
def canonical_root(uri)
|
|
79
|
+
parsed = parse_file_uri(uri)
|
|
80
|
+
decoded = decode_path(parsed.path)
|
|
81
|
+
canonical = File.realpath(decoded)
|
|
82
|
+
unless File.directory?(canonical)
|
|
83
|
+
raise ProtocolError.new("workspace root is not a directory: #{uri}", code: -32_602)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
canonical
|
|
87
|
+
rescue URI::InvalidURIError, ArgumentError, SystemCallError => e
|
|
88
|
+
raise ProtocolError.new("invalid workspace root #{uri.inspect}: #{e.message}", code: -32_602)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs (String encoded) -> String
|
|
92
|
+
def decode_path(encoded)
|
|
93
|
+
raise ArgumentError, "file URI contains an invalid percent escape" if
|
|
94
|
+
encoded.match?(/%(?![0-9A-Fa-f]{2})/)
|
|
95
|
+
if encoded.match?(/%(?:2f|5c|00)/i)
|
|
96
|
+
raise ArgumentError, "file URI must not percent-encode a path separator or NUL"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
parser = begin
|
|
100
|
+
URI::RFC2396_PARSER
|
|
101
|
+
rescue NameError
|
|
102
|
+
URI::DEFAULT_PARSER
|
|
103
|
+
end
|
|
104
|
+
decoded = parser.unescape(encoded)
|
|
105
|
+
raise ArgumentError, "file URI path must be valid UTF-8" unless decoded.valid_encoding?
|
|
106
|
+
raise ArgumentError, "file URI path must not contain NUL" if decoded.include?("\0")
|
|
107
|
+
raise ArgumentError, "file URI path must not contain parent traversal" if
|
|
108
|
+
decoded.split(%r{[\\/]}).include?("..")
|
|
109
|
+
|
|
110
|
+
decoded
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @rbs (String path, String root) -> bool
|
|
114
|
+
def inside?(path, root)
|
|
115
|
+
cursor = path
|
|
116
|
+
loop do
|
|
117
|
+
return true if cursor == root
|
|
118
|
+
|
|
119
|
+
parent = File.dirname(cursor)
|
|
120
|
+
return false if parent == cursor
|
|
121
|
+
|
|
122
|
+
cursor = parent
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|