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
|
@@ -27,6 +27,7 @@ module Ibex
|
|
|
27
27
|
:type, #: Symbol
|
|
28
28
|
:value, #: token_value
|
|
29
29
|
:location, #: Location
|
|
30
|
+
:span, #: SourceSpan?
|
|
30
31
|
keyword_init: true
|
|
31
32
|
)
|
|
32
33
|
|
|
@@ -42,31 +43,53 @@ module Ibex
|
|
|
42
43
|
attr_reader :source #: String
|
|
43
44
|
attr_reader :file #: String
|
|
44
45
|
attr_reader :index #: Integer
|
|
46
|
+
attr_reader :byte_offset #: Integer
|
|
45
47
|
attr_reader :line #: Integer
|
|
46
48
|
attr_reader :column #: Integer
|
|
47
49
|
|
|
48
50
|
# @rbs (String source, String file) -> void
|
|
49
51
|
def initialize(source, file)
|
|
50
|
-
@source = source
|
|
51
|
-
@file = file
|
|
52
|
+
@source = SourceEncoding.validated_utf8(source, file)
|
|
53
|
+
@file = file.dup.freeze
|
|
54
|
+
@ascii_only = @source.ascii_only?
|
|
55
|
+
@character_length = @ascii_only ? @source.bytesize : @source.length
|
|
52
56
|
@index = 0
|
|
57
|
+
@byte_offset = 0
|
|
53
58
|
@line = 1
|
|
54
59
|
@column = 1
|
|
55
60
|
end
|
|
56
61
|
|
|
57
62
|
# @rbs () -> bool
|
|
58
63
|
def eof?
|
|
59
|
-
@index >= @
|
|
64
|
+
@index >= @character_length
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
# @rbs (?Integer offset) -> String?
|
|
63
68
|
def peek(offset = 0)
|
|
64
|
-
@
|
|
69
|
+
character_index = @index + offset
|
|
70
|
+
if @ascii_only
|
|
71
|
+
return if character_index >= @character_length
|
|
72
|
+
|
|
73
|
+
return @source.byteslice(character_index, 1)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
character_index += @character_length if character_index.negative?
|
|
77
|
+
return if character_index.negative? || character_index >= @character_length
|
|
78
|
+
|
|
79
|
+
start_byte = if character_index == @index
|
|
80
|
+
@byte_offset
|
|
81
|
+
elsif character_index == @index + 1
|
|
82
|
+
@byte_offset + utf8_character_width(@byte_offset)
|
|
83
|
+
else
|
|
84
|
+
byte_offset_for_character(character_index)
|
|
85
|
+
end
|
|
86
|
+
@source.byteslice(start_byte, utf8_character_width(start_byte))
|
|
65
87
|
end
|
|
66
88
|
|
|
67
89
|
# @rbs () -> String
|
|
68
90
|
def rest
|
|
69
|
-
@source
|
|
91
|
+
@source.byteslice(@byte_offset, @source.bytesize - @byte_offset) ||
|
|
92
|
+
String.new(encoding: Encoding::UTF_8)
|
|
70
93
|
end
|
|
71
94
|
|
|
72
95
|
# @rbs () -> Location
|
|
@@ -74,14 +97,34 @@ module Ibex
|
|
|
74
97
|
Location.new(file: @file, line: @line, column: @column)
|
|
75
98
|
end
|
|
76
99
|
|
|
100
|
+
# @rbs () -> SourcePosition
|
|
101
|
+
def position
|
|
102
|
+
SourcePosition.new(byte_offset: @byte_offset, line: @line, column: @column)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (SourcePosition start) -> SourceSpan
|
|
106
|
+
def span_from(start)
|
|
107
|
+
SourceSpan.new(file: @file, start: start, finish: position)
|
|
108
|
+
end
|
|
109
|
+
|
|
77
110
|
# @rbs (?Integer count) -> void
|
|
78
111
|
def advance(count = 1)
|
|
112
|
+
return advance_utf8(count) unless @ascii_only
|
|
113
|
+
|
|
114
|
+
advance_ascii(count)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# @rbs (Integer count) -> void
|
|
120
|
+
def advance_ascii(count)
|
|
79
121
|
count.times do
|
|
80
|
-
|
|
81
|
-
break unless character
|
|
122
|
+
break if @index >= @character_length
|
|
82
123
|
|
|
124
|
+
newline = @source.getbyte(@index) == 10
|
|
83
125
|
@index += 1
|
|
84
|
-
|
|
126
|
+
@byte_offset = @index
|
|
127
|
+
if newline
|
|
85
128
|
@line += 1
|
|
86
129
|
@column = 1
|
|
87
130
|
else
|
|
@@ -89,6 +132,87 @@ module Ibex
|
|
|
89
132
|
end
|
|
90
133
|
end
|
|
91
134
|
end
|
|
135
|
+
|
|
136
|
+
# @rbs (Integer count) -> void
|
|
137
|
+
def advance_utf8(count)
|
|
138
|
+
count.times do
|
|
139
|
+
break if @index >= @character_length
|
|
140
|
+
|
|
141
|
+
leading_byte = @source.getbyte(@byte_offset)
|
|
142
|
+
raise Ibex::Error, "#{@file}: input must be valid UTF-8" unless leading_byte
|
|
143
|
+
|
|
144
|
+
@index += 1
|
|
145
|
+
@byte_offset += if leading_byte < 0x80
|
|
146
|
+
1
|
|
147
|
+
elsif leading_byte < 0xE0
|
|
148
|
+
2
|
|
149
|
+
elsif leading_byte < 0xF0
|
|
150
|
+
3
|
|
151
|
+
else
|
|
152
|
+
4
|
|
153
|
+
end
|
|
154
|
+
if leading_byte == 10
|
|
155
|
+
@line += 1
|
|
156
|
+
@column = 1
|
|
157
|
+
else
|
|
158
|
+
@column += 1
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @rbs (Integer character_index) -> Integer
|
|
164
|
+
def byte_offset_for_character(character_index)
|
|
165
|
+
start_distance = character_index
|
|
166
|
+
current_distance = (character_index - @index).abs
|
|
167
|
+
finish_distance = @character_length - character_index
|
|
168
|
+
|
|
169
|
+
if current_distance <= start_distance && current_distance <= finish_distance
|
|
170
|
+
walk_to_character(@byte_offset, @index, character_index)
|
|
171
|
+
elsif start_distance <= finish_distance
|
|
172
|
+
walk_to_character(0, 0, character_index)
|
|
173
|
+
else
|
|
174
|
+
walk_to_character(@source.bytesize, @character_length, character_index)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs (Integer byte_offset, Integer from_index, Integer to_index) -> Integer
|
|
179
|
+
def walk_to_character(byte_offset, from_index, to_index)
|
|
180
|
+
while from_index < to_index
|
|
181
|
+
byte_offset += utf8_character_width(byte_offset)
|
|
182
|
+
from_index += 1
|
|
183
|
+
end
|
|
184
|
+
while from_index > to_index
|
|
185
|
+
byte_offset = previous_character_byte_offset(byte_offset)
|
|
186
|
+
from_index -= 1
|
|
187
|
+
end
|
|
188
|
+
byte_offset
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @rbs (Integer byte_offset) -> Integer
|
|
192
|
+
def previous_character_byte_offset(byte_offset)
|
|
193
|
+
byte_offset -= 1
|
|
194
|
+
while (byte = @source.getbyte(byte_offset)) && continuation_byte?(byte)
|
|
195
|
+
byte_offset -= 1
|
|
196
|
+
end
|
|
197
|
+
byte_offset
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @rbs (Integer byte_offset) -> Integer
|
|
201
|
+
def utf8_character_width(byte_offset)
|
|
202
|
+
leading_byte = @source.getbyte(byte_offset)
|
|
203
|
+
raise Ibex::Error, "#{@file}: input must be valid UTF-8" unless leading_byte
|
|
204
|
+
|
|
205
|
+
return 1 if leading_byte < 0x80
|
|
206
|
+
return 2 if leading_byte < 0xE0
|
|
207
|
+
return 3 if leading_byte < 0xF0
|
|
208
|
+
|
|
209
|
+
4
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# @rbs (Integer byte) -> bool
|
|
213
|
+
def continuation_byte?(byte)
|
|
214
|
+
byte >= 0x80 && byte < 0xC0
|
|
215
|
+
end
|
|
92
216
|
end
|
|
93
217
|
end
|
|
94
218
|
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# An immutable lexical unit or trivia slice in a concrete syntax tree.
|
|
6
|
+
class Segment
|
|
7
|
+
TRIVIA_KINDS = %i[whitespace newline line_comment block_comment].freeze #: Array[Symbol]
|
|
8
|
+
OPAQUE_KINDS = %i[action user_code_body].freeze #: Array[Symbol]
|
|
9
|
+
|
|
10
|
+
attr_reader :kind #: Symbol
|
|
11
|
+
attr_reader :span #: SourceSpan
|
|
12
|
+
attr_reader :text #: String
|
|
13
|
+
attr_reader :token_type #: Symbol?
|
|
14
|
+
attr_reader :token_index #: Integer?
|
|
15
|
+
|
|
16
|
+
# @rbs (kind: Symbol, span: SourceSpan, text: String, ?token_type: Symbol?,
|
|
17
|
+
# ?token_index: Integer?) -> void
|
|
18
|
+
def initialize(kind:, span:, text:, token_type: nil, token_index: nil)
|
|
19
|
+
raise ArgumentError, "segment text does not match its span" unless text.bytesize == span.length
|
|
20
|
+
|
|
21
|
+
@kind = kind
|
|
22
|
+
@span = span
|
|
23
|
+
@text = text.dup.freeze
|
|
24
|
+
@token_type = token_type
|
|
25
|
+
@token_index = token_index
|
|
26
|
+
freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @rbs () -> bool
|
|
30
|
+
def trivia?
|
|
31
|
+
TRIVIA_KINDS.include?(kind)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @rbs () -> bool
|
|
35
|
+
def opaque?
|
|
36
|
+
OPAQUE_KINDS.include?(kind)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @rbs () -> bool
|
|
40
|
+
def token?
|
|
41
|
+
!token_index.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @rbs () -> String
|
|
45
|
+
def render
|
|
46
|
+
text
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module CST
|
|
51
|
+
# The immutable lexical root of a grammar source document.
|
|
52
|
+
class Document
|
|
53
|
+
# @rbs! include Enumerable[Segment]
|
|
54
|
+
# @rbs skip
|
|
55
|
+
include Enumerable
|
|
56
|
+
|
|
57
|
+
attr_reader :segments #: Array[Segment]
|
|
58
|
+
|
|
59
|
+
# @rbs (Array[Segment] segments) -> void
|
|
60
|
+
def initialize(segments)
|
|
61
|
+
validate_contiguous(segments)
|
|
62
|
+
@segments = segments.dup.freeze
|
|
63
|
+
freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @rbs!
|
|
67
|
+
# def each: () -> Enumerator[Segment, Document]
|
|
68
|
+
# | () { (Segment) -> void } -> Document
|
|
69
|
+
# @rbs skip
|
|
70
|
+
def each(&block)
|
|
71
|
+
return enum_for(:each) unless block
|
|
72
|
+
|
|
73
|
+
segments.each(&block)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs () -> String
|
|
78
|
+
def render
|
|
79
|
+
segments.map(&:render).join
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# @rbs (Array[Segment] segments) -> void
|
|
85
|
+
def validate_contiguous(segments)
|
|
86
|
+
segments.each_cons(2) do |left, right|
|
|
87
|
+
next unless left && right
|
|
88
|
+
next if left.span.end_byte == right.span.start_byte
|
|
89
|
+
|
|
90
|
+
raise ArgumentError, "concrete syntax tree segments must be contiguous"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Lossless grammar source plus its semantic parse and lexical concrete syntax tree.
|
|
97
|
+
class SourceDocument
|
|
98
|
+
attr_reader :source #: String
|
|
99
|
+
attr_reader :file #: String
|
|
100
|
+
attr_reader :tokens #: Array[Token]
|
|
101
|
+
attr_reader :cst #: CST::Document
|
|
102
|
+
attr_reader :ast #: AST::Root | AST::Fragment | nil
|
|
103
|
+
|
|
104
|
+
# @rbs (source: String, file: String, tokens: Array[Token], cst: CST::Document,
|
|
105
|
+
# ?ast: AST::Root | AST::Fragment | nil) -> void
|
|
106
|
+
def initialize(source:, file:, tokens:, cst:, ast: nil)
|
|
107
|
+
@source = SourceEncoding.validated_utf8(source, file)
|
|
108
|
+
@file = file.dup.freeze
|
|
109
|
+
@tokens = tokens.dup.freeze
|
|
110
|
+
@cst = cst
|
|
111
|
+
@ast = ast
|
|
112
|
+
@line_starts = build_line_starts.freeze #: Array[Integer]
|
|
113
|
+
validate_render
|
|
114
|
+
freeze
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @rbs () -> String
|
|
118
|
+
def render
|
|
119
|
+
cst.render
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @rbs (SourceSpan span) -> String
|
|
123
|
+
def slice(span)
|
|
124
|
+
validate_span(span)
|
|
125
|
+
source.byteslice(span.start_byte, span.length) || ""
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @rbs (Integer byte_offset) -> SourcePosition
|
|
129
|
+
def position_at(byte_offset)
|
|
130
|
+
validate_byte_offset(byte_offset)
|
|
131
|
+
prefix = source.byteslice(0, byte_offset) || ""
|
|
132
|
+
raise ArgumentError, "byte offset must be on a UTF-8 character boundary" unless prefix.valid_encoding?
|
|
133
|
+
|
|
134
|
+
line_index = (@line_starts.bsearch_index { |start| start > byte_offset } || @line_starts.length) - 1
|
|
135
|
+
line_start = @line_starts.fetch(line_index)
|
|
136
|
+
line_prefix = source.byteslice(line_start, byte_offset - line_start) || ""
|
|
137
|
+
SourcePosition.new(byte_offset: byte_offset, line: line_index + 1, column: line_prefix.length + 1)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @rbs (Integer line, Integer column) -> Integer
|
|
141
|
+
def byte_offset_at(line, column)
|
|
142
|
+
raise ArgumentError, "line and column must be positive" unless line.positive? && column.positive?
|
|
143
|
+
|
|
144
|
+
line_start = @line_starts[line - 1]
|
|
145
|
+
raise ArgumentError, "line is outside the source" unless line_start
|
|
146
|
+
|
|
147
|
+
line_end = @line_starts[line] || source.bytesize
|
|
148
|
+
line_text = source.byteslice(line_start, line_end - line_start) || ""
|
|
149
|
+
line_text = line_text.delete_suffix("\n")
|
|
150
|
+
character_count = column - 1
|
|
151
|
+
raise ArgumentError, "column is outside the source line" if character_count > line_text.length
|
|
152
|
+
|
|
153
|
+
line_start + line_text.each_char.take(character_count).join.bytesize
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @rbs (Integer start_byte, Integer end_byte) -> SourceSpan
|
|
157
|
+
def span(start_byte, end_byte)
|
|
158
|
+
SourceSpan.new(file: file, start: position_at(start_byte), finish: position_at(end_byte))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @rbs (Segment segment) -> Token?
|
|
162
|
+
def token_for(segment)
|
|
163
|
+
index = segment.token_index
|
|
164
|
+
index && tokens[index]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @rbs (AST::Root | AST::Fragment ast) -> SourceDocument
|
|
168
|
+
def with_ast(ast)
|
|
169
|
+
self.class.new(source: source, file: file, tokens: tokens, cst: cst, ast: ast)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
# @rbs () -> Array[Integer]
|
|
175
|
+
def build_line_starts
|
|
176
|
+
starts = [0]
|
|
177
|
+
offset = 0
|
|
178
|
+
binary_source = source.b
|
|
179
|
+
while (newline = binary_source.index("\n", offset))
|
|
180
|
+
offset = newline + 1
|
|
181
|
+
starts << offset
|
|
182
|
+
end
|
|
183
|
+
starts
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @rbs () -> void
|
|
187
|
+
def validate_render
|
|
188
|
+
return if full_source_coverage? && segment_slices_match? && render == source
|
|
189
|
+
|
|
190
|
+
raise ArgumentError, "concrete syntax tree does not reproduce its source"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @rbs () -> bool
|
|
194
|
+
def full_source_coverage?
|
|
195
|
+
segments = cst.segments
|
|
196
|
+
return source.empty? if segments.empty?
|
|
197
|
+
|
|
198
|
+
first = segments.first
|
|
199
|
+
last = segments.last
|
|
200
|
+
return false unless first && last
|
|
201
|
+
|
|
202
|
+
first.span.start_byte.zero? && last.span.end_byte == source.bytesize
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @rbs () -> bool
|
|
206
|
+
def segment_slices_match?
|
|
207
|
+
cst.segments.all? do |segment|
|
|
208
|
+
segment.span.file == file &&
|
|
209
|
+
source.byteslice(segment.span.start_byte, segment.span.length) == segment.text
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# @rbs (Integer byte_offset) -> void
|
|
214
|
+
def validate_byte_offset(byte_offset)
|
|
215
|
+
return if byte_offset.between?(0, source.bytesize)
|
|
216
|
+
|
|
217
|
+
raise ArgumentError, "byte offset is outside the source"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @rbs (SourceSpan span) -> void
|
|
221
|
+
def validate_span(span)
|
|
222
|
+
raise ArgumentError, "span belongs to another source" unless span.file == file
|
|
223
|
+
|
|
224
|
+
validate_byte_offset(span.start_byte)
|
|
225
|
+
validate_byte_offset(span.end_byte)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../generation_input"
|
|
4
|
+
|
|
5
|
+
module Ibex
|
|
6
|
+
module Frontend
|
|
7
|
+
# Resolves canonical grammar paths and reads either open-buffer overlays or disk.
|
|
8
|
+
class SourceLoader
|
|
9
|
+
# @rbs @overlays: Hash[String, String]
|
|
10
|
+
# @rbs @aliases: Hash[String, String]
|
|
11
|
+
# @rbs @read_records: Hash[String, GenerationInput]
|
|
12
|
+
# @rbs @record_reads: bool
|
|
13
|
+
# @rbs @access_paths: Hash[String, Array[String]]
|
|
14
|
+
|
|
15
|
+
# @rbs () -> Array[GenerationInput]
|
|
16
|
+
def read_records
|
|
17
|
+
@read_records.values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Associate the lexical path used by a resolver with its canonical source.
|
|
21
|
+
# @rbs (String canonical, String access_path) -> void
|
|
22
|
+
def record_access(canonical, access_path)
|
|
23
|
+
paths = (@access_paths[canonical] ||= [])
|
|
24
|
+
expanded = File.expand_path(access_path)
|
|
25
|
+
paths << expanded unless paths.include?(expanded)
|
|
26
|
+
@read_records[canonical]&.add_access_path(expanded)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @rbs (?overlays: Hash[String, String], ?record_reads: bool) -> void
|
|
30
|
+
def initialize(overlays: {}, record_reads: false)
|
|
31
|
+
@overlays = {} #: Hash[String, String]
|
|
32
|
+
@aliases = {} #: Hash[String, String]
|
|
33
|
+
@read_records = {} #: Hash[String, GenerationInput]
|
|
34
|
+
@record_reads = record_reads
|
|
35
|
+
@access_paths = {} #: Hash[String, Array[String]]
|
|
36
|
+
overlays.each { |path, source| set_overlay(path, source) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Canonicalize an existing file, or a missing path explicitly allowed by an open overlay.
|
|
40
|
+
# @rbs (String path, ?base: String, ?allow_missing: bool) -> String
|
|
41
|
+
def canonical_path(path, base: Dir.pwd, allow_missing: false)
|
|
42
|
+
expanded = File.expand_path(path, base)
|
|
43
|
+
aliased = @aliases[expanded]
|
|
44
|
+
return aliased if aliased
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
File.realpath(expanded)
|
|
48
|
+
rescue Errno::ENOENT
|
|
49
|
+
canonical = canonical_missing_path(expanded)
|
|
50
|
+
return canonical if allow_missing || @overlays.key?(canonical)
|
|
51
|
+
|
|
52
|
+
raise
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs (String path) -> String
|
|
57
|
+
def read(path)
|
|
58
|
+
canonical = canonical_path(path, allow_missing: true)
|
|
59
|
+
overlay = @overlays[canonical]
|
|
60
|
+
source = overlay || File.binread(canonical)
|
|
61
|
+
if @record_reads
|
|
62
|
+
record = GenerationInput.new(canonical, source)
|
|
63
|
+
(@access_paths[canonical] || []).each { |access| record.add_access_path(access) }
|
|
64
|
+
@read_records[canonical] = record
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
source
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @rbs (String path) -> bool
|
|
71
|
+
def file?(path)
|
|
72
|
+
canonical = canonical_path(path, allow_missing: true)
|
|
73
|
+
@overlays.key?(canonical) || File.file?(canonical)
|
|
74
|
+
rescue SystemCallError
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @rbs (String path) -> bool
|
|
79
|
+
def overlay?(path)
|
|
80
|
+
canonical = canonical_path(path, allow_missing: true)
|
|
81
|
+
@overlays.key?(canonical)
|
|
82
|
+
rescue SystemCallError
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Install or replace an editor buffer without writing it to disk.
|
|
87
|
+
# @rbs (String path, String source) -> String
|
|
88
|
+
def set_overlay(path, source)
|
|
89
|
+
expanded = File.expand_path(path)
|
|
90
|
+
canonical = canonical_path(expanded, allow_missing: true)
|
|
91
|
+
raise ArgumentError, "overlay path must not be a directory" if File.directory?(canonical)
|
|
92
|
+
|
|
93
|
+
@aliases[expanded] = canonical
|
|
94
|
+
@overlays[canonical] = source.dup.freeze
|
|
95
|
+
canonical
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Remove an editor buffer and return its canonical path.
|
|
99
|
+
# @rbs (String path) -> String
|
|
100
|
+
def delete_overlay(path)
|
|
101
|
+
expanded = File.expand_path(path)
|
|
102
|
+
canonical = @aliases.delete(expanded) || canonical_path(expanded, allow_missing: true)
|
|
103
|
+
@aliases.delete_if { |_alias_path, target| target == canonical }
|
|
104
|
+
@overlays.delete(canonical)
|
|
105
|
+
canonical
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Read disk while deliberately bypassing an open overlay.
|
|
109
|
+
# @rbs (String path) -> String
|
|
110
|
+
def disk_source(path)
|
|
111
|
+
File.binread(disk_path(path))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @rbs (String path) -> bool
|
|
115
|
+
def disk_file?(path)
|
|
116
|
+
File.file?(disk_path(path))
|
|
117
|
+
rescue SystemCallError
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Resolve the current disk target without consulting overlay aliases.
|
|
122
|
+
# @rbs (String path) -> String
|
|
123
|
+
def disk_path(path)
|
|
124
|
+
File.realpath(File.expand_path(path))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Resolve the nearest existing ancestor so symlink escapes remain visible for new files.
|
|
130
|
+
# @rbs (String expanded) -> String
|
|
131
|
+
def canonical_missing_path(expanded)
|
|
132
|
+
raise Errno::ENOENT, expanded if File.symlink?(expanded)
|
|
133
|
+
|
|
134
|
+
cursor = expanded
|
|
135
|
+
suffix = [] #: Array[String]
|
|
136
|
+
loop do
|
|
137
|
+
parent = File.dirname(cursor)
|
|
138
|
+
raise Errno::ENOENT, expanded if parent == cursor
|
|
139
|
+
|
|
140
|
+
suffix.unshift(File.basename(cursor))
|
|
141
|
+
cursor = parent
|
|
142
|
+
break if File.exist?(cursor) || File.symlink?(cursor)
|
|
143
|
+
end
|
|
144
|
+
raise Errno::ENOTDIR, cursor unless File.directory?(File.realpath(cursor))
|
|
145
|
+
|
|
146
|
+
File.join(File.realpath(cursor), *suffix)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Normalizes grammar source without changing its bytes.
|
|
6
|
+
module SourceEncoding
|
|
7
|
+
# @rbs (String source, String file) -> String
|
|
8
|
+
def self.validated_utf8(source, file)
|
|
9
|
+
validated = source.dup.force_encoding(Encoding::UTF_8)
|
|
10
|
+
raise Ibex::Error, "#{file}: input must be valid UTF-8" unless validated.valid_encoding?
|
|
11
|
+
|
|
12
|
+
validated.freeze
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# An immutable position in grammar source.
|
|
17
|
+
class SourcePosition
|
|
18
|
+
attr_reader :byte_offset #: Integer
|
|
19
|
+
attr_reader :line #: Integer
|
|
20
|
+
attr_reader :column #: Integer
|
|
21
|
+
|
|
22
|
+
# @rbs (byte_offset: Integer, line: Integer, column: Integer) -> void
|
|
23
|
+
def initialize(byte_offset:, line:, column:)
|
|
24
|
+
raise ArgumentError, "byte_offset must be non-negative" if byte_offset.negative?
|
|
25
|
+
raise ArgumentError, "line and column must be positive" unless line.positive? && column.positive?
|
|
26
|
+
|
|
27
|
+
@byte_offset = byte_offset
|
|
28
|
+
@line = line
|
|
29
|
+
@column = column
|
|
30
|
+
freeze
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @rbs () -> Hash[Symbol, Integer]
|
|
34
|
+
def to_h
|
|
35
|
+
{ byte_offset: byte_offset, line: line, column: column }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# An immutable half-open byte span in one grammar source.
|
|
40
|
+
class SourceSpan
|
|
41
|
+
attr_reader :file #: String
|
|
42
|
+
attr_reader :start #: SourcePosition
|
|
43
|
+
attr_reader :finish #: SourcePosition
|
|
44
|
+
|
|
45
|
+
# @rbs (file: String, start: SourcePosition, finish: SourcePosition) -> void
|
|
46
|
+
def initialize(file:, start:, finish:)
|
|
47
|
+
raise ArgumentError, "span end precedes its start" if finish.byte_offset < start.byte_offset
|
|
48
|
+
|
|
49
|
+
@file = file.dup.freeze
|
|
50
|
+
@start = start
|
|
51
|
+
@finish = finish
|
|
52
|
+
freeze
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs () -> Integer
|
|
56
|
+
def start_byte
|
|
57
|
+
start.byte_offset
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs () -> Integer
|
|
61
|
+
def end_byte
|
|
62
|
+
finish.byte_offset
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @rbs () -> Integer
|
|
66
|
+
def length
|
|
67
|
+
end_byte - start_byte
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @rbs () -> bool
|
|
71
|
+
def empty?
|
|
72
|
+
length.zero?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
76
|
+
def to_h
|
|
77
|
+
{ file: file, start: start.to_h, end: finish.to_h }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
class TokenAdapter
|
|
6
|
+
# Root/fragment header and include-path transitions for declaration classification.
|
|
7
|
+
module DeclarationDocumentState
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# @rbs (Token token) -> external_token
|
|
11
|
+
def class_keyword(token)
|
|
12
|
+
# @type self: DeclarationState
|
|
13
|
+
value = string_value(token)
|
|
14
|
+
if value == "fragment"
|
|
15
|
+
raise Ibex::Error, "#{token.location}: fragments require extended mode" unless @extended_mode
|
|
16
|
+
|
|
17
|
+
@fragment = true
|
|
18
|
+
@state = :declaration
|
|
19
|
+
return :FRAGMENT
|
|
20
|
+
end
|
|
21
|
+
return :IDENTIFIER unless value == "class"
|
|
22
|
+
|
|
23
|
+
@state = :class_name
|
|
24
|
+
:CLASS
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @rbs (external_token type) -> external_token?
|
|
28
|
+
def classify_include(type)
|
|
29
|
+
# @type self: DeclarationState
|
|
30
|
+
return unless @state == :include_path && type == :LITERAL
|
|
31
|
+
|
|
32
|
+
@state = :declaration
|
|
33
|
+
@declaration = nil
|
|
34
|
+
type
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @rbs (Token token, String value) -> void
|
|
38
|
+
def reject_fragment_pragma(token, value)
|
|
39
|
+
# @type self: DeclarationState
|
|
40
|
+
return unless @fragment && value == "pragma"
|
|
41
|
+
|
|
42
|
+
raise Ibex::Error, "#{token.location}: pragma declarations are not allowed in fragments"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|