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
data/docs/grammar-reference.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Grammar reference
|
|
2
2
|
|
|
3
|
-
Ibex's default
|
|
3
|
+
Ibex's `default` mode accepts the compatible grammar described here. `--mode=extended` or an explicit grammar-file
|
|
4
4
|
`pragma extended` adds the marked syntax; extensions are never inferred from a production.
|
|
5
5
|
|
|
6
6
|
## File structure
|
|
@@ -8,6 +8,7 @@ Ibex's default `racc` mode accepts the compatible grammar described here. `--mod
|
|
|
8
8
|
```text
|
|
9
9
|
class Namespace::Parser < OptionalSuperclass
|
|
10
10
|
pragma extended # optional; must precede ordinary declarations
|
|
11
|
+
pragma cst # optional automatic concrete-tree mode
|
|
11
12
|
declarations
|
|
12
13
|
rule
|
|
13
14
|
productions
|
|
@@ -23,23 +24,232 @@ Ruby copied after the parser class
|
|
|
23
24
|
The superclass defaults to `Ibex::Runtime::Parser`. Repeated user-code blocks retain their source order and are concatenated.
|
|
24
25
|
Grammar comments use `#` through end of line or `/* ... */`.
|
|
25
26
|
|
|
27
|
+
Extended roots may import explicit fragment files:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
fragment
|
|
31
|
+
token SHARED
|
|
32
|
+
import "nested/expressions.y"
|
|
33
|
+
rule
|
|
34
|
+
shared_rule: SHARED
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Fragments own no class, superclass, pragma, options, expected-conflict count, start symbol, or user-code blocks. Their `rule`
|
|
39
|
+
section may be empty. Token, precedence, conversion, display, and type declarations are merged with their original locations.
|
|
40
|
+
`import "relative/path.y"` appears in the declaration section of a root or fragment and requires extended mode. The older
|
|
41
|
+
`include` spelling remains a compatible alias. Paths are
|
|
42
|
+
resolved relative to the including file, must be double-quoted and relative, cannot contain parent traversal, globs, or NUL, and
|
|
43
|
+
must resolve through symlinks to a regular file below the root grammar's canonical directory.
|
|
44
|
+
|
|
45
|
+
Resolution is deterministic depth-first order at include sites. A canonical file reached twice through a diamond is merged only
|
|
46
|
+
at its first occurrence; a cycle reports its exact canonical path loop. `Frontend::Resolver.new(path, mode: :extended).resolve`
|
|
47
|
+
returns the merged root and canonical dependency closure. `Parser#parse_fragment` parses fragment text without I/O, while normal
|
|
48
|
+
`Parser#parse` continues to return only a root and rejects fragment input. The exposed resolution is recursively immutable,
|
|
49
|
+
including AST strings and locations and defensively copied include provenance. Canonical directory ancestry, rather than a
|
|
50
|
+
string-prefix comparison, enforces the source-root boundary and remains valid when the source root is a filesystem or drive root.
|
|
51
|
+
|
|
52
|
+
Immediately preceding `##` line comments attach documentation to a rule:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
## First line
|
|
56
|
+
## one extra leading space is retained
|
|
57
|
+
value: TOKEN
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Indentation before the comment is ignored. Ibex removes `##` and one optional space, so the example stores
|
|
61
|
+
`"First line\n one extra leading space is retained"`. The lines must be consecutive and directly above the LHS. Blank lines,
|
|
62
|
+
ordinary `#` comments, block comments, grammar tokens, and opaque actions or heredocs break attachment. The association uses
|
|
63
|
+
lossless segment spans rather than rescanning raw text. Documentation works in roots and fragments and is nullable on
|
|
64
|
+
`AST::Rule`.
|
|
65
|
+
|
|
66
|
+
For repeated definitions, every normalized user production keeps its definition's documentation. The nonterminal symbol uses
|
|
67
|
+
the first nonnil text; a different later nonnil text is a positioned error, while the same text is accepted. Grammar IR v2
|
|
68
|
+
serializes `doc` on symbols and productions. Version 1 omits those fields.
|
|
69
|
+
|
|
70
|
+
`ibex doc [--format=markdown|html|railroad] [-o FILE] [--mode=MODE] grammar.y` renders the canonical resolved grammar. Output
|
|
71
|
+
defaults to Markdown on stdout. HTML is accessible and self-contained, railroad output includes visible wrapped descriptions,
|
|
72
|
+
and all formats escape grammar-controlled text. `-o` writes atomically and cannot alias the input grammar.
|
|
73
|
+
|
|
74
|
+
The programmatic frontend can preserve this file exactly with
|
|
75
|
+
`Ibex::Frontend::Parser.new(source, file: path).parse_document`. The returned source document contains the same semantic AST as
|
|
76
|
+
`parse` plus immutable token, whitespace, line-break, comment, action, user-code marker/body, and EOF segments. `render` reproduces the input
|
|
77
|
+
bytes; spans and `slice` use zero-based half-open byte offsets. Line and column positions remain one-based and count Unicode
|
|
78
|
+
scalar values. Input is interpreted as UTF-8 without transcoding and invalid byte sequences are rejected before lexing.
|
|
79
|
+
`parse_source_document` accepts either a root or, in extended mode, an explicit fragment.
|
|
80
|
+
|
|
81
|
+
`ibex fmt [--mode=default|extended] grammar.y` formats to stdout. `fmt --check FILE...` reports all invalid or noncanonical files,
|
|
82
|
+
and `fmt --write FILE...` validates and stages the complete batch before transactionally replacing changed files. Standard input
|
|
83
|
+
is available as `fmt -`; `--stdin-filename=FILE` supplies its control-byte-free diagnostic name. Check and write modes require
|
|
84
|
+
file paths. Formatting changes only whitespace/newline trivia, retains token, comment, action/heredoc, and user-code bytes in
|
|
85
|
+
order, and preserves existing mixed newline spellings at required boundaries. A new boundary uses the first newline even inside
|
|
86
|
+
opaque text. The output must reparse to an identical location-free AST through a stack-safe iterative comparison and is
|
|
87
|
+
idempotent. Write mode rejects aliased targets, preserves full modes and symlinks, and rolls back every target if rename or
|
|
88
|
+
directory synchronization fails. Same-directory hard-link backups are synchronized before installation. A backup whose target
|
|
89
|
+
could not be restored is retained and reported; cleanup failures after every target was committed are reported as status-0
|
|
90
|
+
warnings because they do not undo the committed update.
|
|
91
|
+
|
|
92
|
+
`ibex lsp [--stdio]` serves the same lossless frontend over LSP 3.17 Content-Length-framed standard IO. Initialization requires a
|
|
93
|
+
local `rootUri` or initial `workspaceFolders`; every document URI must remain within those canonical roots. The server uses
|
|
94
|
+
full-text synchronization and UTF-16 positions. Open buffers override disk throughout fragment resolution, so changing or
|
|
95
|
+
creating an included fragment re-diagnoses all known dependent roots. It supports diagnostics, definition, references,
|
|
96
|
+
prepare-rename, rename, and hover. Rename accepts only defined identifiers, checks scope/collisions, includes open versions in
|
|
97
|
+
the workspace edit, and reparses/re-resolves every affected closure before returning edits. Grammar actions, heredocs, and user
|
|
98
|
+
code are never executed. See [editor setup](editor-setup.md).
|
|
99
|
+
|
|
100
|
+
`parse_with_diagnostics(max_diagnostics: 20)` collects up to the limit independently from the lexical and syntax phases, merges
|
|
101
|
+
them in source order, and returns the globally earliest records. Recovery is deliberately limited to complete declarations,
|
|
102
|
+
rules, or outer alternatives at balanced delimiters, so an error inside a nested group cannot synchronize at that group's `|`.
|
|
103
|
+
The result may expose a partial AST containing later valid rules, but `success?` remains false and the original source document
|
|
104
|
+
does not adopt that AST. `ibex diagnose` renders the same records as text or versioned JSON. After a clean root parse it resolves
|
|
105
|
+
includes and emits the first cross-file security, missing-target, cycle, or fragment-syntax failure as
|
|
106
|
+
`frontend.resolution_error`; cross-file recovery is intentionally bounded to that one record. Permission and other actual
|
|
107
|
+
filesystem read failures remain CLI invocation errors on stderr and do not produce a JSON envelope.
|
|
108
|
+
|
|
26
109
|
## Declarations
|
|
27
110
|
|
|
28
|
-
- `pragma extended` enables extended syntax for this grammar even when the CLI uses its default or explicit `--mode=
|
|
111
|
+
- `pragma extended` enables extended syntax for this grammar even when the CLI uses its default or explicit `--mode=default`.
|
|
29
112
|
It must immediately follow the class header, before every ordinary declaration. Unknown, duplicate, and misplaced pragmas
|
|
30
|
-
are positioned errors. The
|
|
31
|
-
|
|
113
|
+
are positioned errors. The frontend records the effective mode on the root AST, and Grammar IR v2 records extended mode
|
|
114
|
+
additively so downstream generators preserve its runtime behavior.
|
|
115
|
+
- `pragma cst` enables extended syntax and builds a pure-syntax Red/Green tree
|
|
116
|
+
in parallel with the ordinary semantic value stack. Distinct pragmas may be
|
|
117
|
+
combined in the class header; repeating either one is an error. Grammar IR
|
|
118
|
+
v2 stores the optional `cst: true` setting.
|
|
119
|
+
- `import "relative/path.y"` inserts one explicit fragment through the canonical resolver. `include` is an accepted
|
|
120
|
+
compatibility spelling. Imports are available only in extended mode. Parsing source text alone performs no filesystem
|
|
121
|
+
access; path-based callers use `Frontend::Resolver` to resolve the import graph.
|
|
122
|
+
- `## text` is a lossless line comment rather than a parser declaration. A consecutive block immediately above a rule supplies
|
|
123
|
+
its documentation as described above.
|
|
32
124
|
- `token NAME ...` declares terminals for typo diagnostics. It is optional. Uppercase names and quoted strings are terminals;
|
|
33
|
-
lowercase names are nonterminals unless they are `error`.
|
|
125
|
+
lowercase names are nonterminals unless they are `error`. In extended mode, `token PLUS "+"` declares `PLUS` and assigns
|
|
126
|
+
`"+"` as its display name; the dedicated `display` declaration remains available.
|
|
34
127
|
- A `prechigh ... preclow` block lists precedence from high to low; `preclow ... prechigh` lists it from low to high. Each level
|
|
35
|
-
begins with `left`, `right`, or `nonassoc` followed by one or more terminals.
|
|
128
|
+
begins with `left`, `right`, or `nonassoc` followed by one or more terminals. Extended `%precedence` assigns a level without
|
|
129
|
+
associativity; an equal-level shift/reduce choice therefore remains an unresolved, counted default shift.
|
|
36
130
|
- `options no_result_var` makes an action's final expression its value. `omit_action_call` is enabled by default;
|
|
37
131
|
`no_omit_action_call` disables it.
|
|
38
132
|
- `expect N` suppresses the warning when exactly N unresolved shift/reduce conflicts remain. Conflicts resolved by precedence are
|
|
39
133
|
retained in Automaton IR but are not counted.
|
|
40
|
-
-
|
|
134
|
+
- Extended `%expect-rr N` records the expected reduce/reduce count. Under `--warnings=error`, generation succeeds only when
|
|
135
|
+
both declared counts match.
|
|
136
|
+
- `start name` overrides the first rule as the start symbol. Extended mode accepts an ordered list such as
|
|
137
|
+
`start program expression`. The first name remains the primary entry for `do_parse`; generated parsers also expose
|
|
138
|
+
`parse_program` and `parse_expression`. Shared construction attributes each conflict to its reachable entries and marks a
|
|
139
|
+
conflict as composite when it exists only after their LALR states are merged. `--entry-isolation` builds disjoint state sets
|
|
140
|
+
for every entry and can remove such composite conflicts at the cost of a larger table.
|
|
141
|
+
- `Parser#expected_tokens_exact` simulates reductions and gotos on a private stack to report only viable lookaheads. Extended
|
|
142
|
+
grammars use this LAC result for `expected_tokens`; compatible grammars keep the historical current-state result.
|
|
41
143
|
- `convert ... end` changes external token objects. The second column is a quoted string containing Ruby source, not the value
|
|
42
144
|
itself: `NUM ':number'` uses `:number`, while `NUM '"number"'` uses the String `"number"`.
|
|
145
|
+
- Extended mode accepts `display SYMBOL "human name"` to give a terminal or nonterminal a human-facing label without changing
|
|
146
|
+
its identity. Runtime errors, `expected_tokens`, and text, graph, and HTML reports prefer that label.
|
|
147
|
+
- Extended mode accepts `type SYMBOL "RBS type"` to describe the symbol's semantic value. Display labels and type spellings
|
|
148
|
+
must be non-empty quoted values on the declaration line. Type spellings are copied as opaque RBS and should be checked with
|
|
149
|
+
normal RBS validation. A type declared for an eliminated `%inline` rule is retained on its composition-plan result; a display
|
|
150
|
+
label for an eliminated inline rule is rejected because no runtime or diagnostic symbol remains to consume it.
|
|
151
|
+
- Extended roots accept one `%recover sync: TOKEN ...` declaration. Every name must be a unique declared terminal other than
|
|
152
|
+
`error`. When explicit yacc recovery cannot shift `error`, the runtime discards through the first listed synchronization
|
|
153
|
+
token it encounters, pops to a state that accepts it, and then processes that retained token normally.
|
|
154
|
+
- Extended roots accept repeated `%on_error_reduce NAME ...` declarations for nonterminals. Names on one line share a priority;
|
|
155
|
+
each later declaration has higher priority. A uniquely highest-priority completed production fills only table cells that
|
|
156
|
+
would otherwise be errors, so explicit shifts, reductions, accepts, and conflict decisions remain authoritative.
|
|
157
|
+
- Extended roots accept ordered `%test accept "source"` and `%test reject "source"` declarations. Sources must use
|
|
158
|
+
double-quoted Ruby literals and exact duplicate expectation/source pairs are rejected. Grammar IR v2 retains their decoded
|
|
159
|
+
source and location; ordinary generated parser tables do not.
|
|
160
|
+
|
|
161
|
+
## Generated lexer (extended mode)
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
lexer
|
|
165
|
+
skip /\s+/
|
|
166
|
+
NUMBER /\d+/ { |text| Integer(text, 10) }
|
|
167
|
+
state STRING do
|
|
168
|
+
on '"' { pop_state; emit :STRING_END }
|
|
169
|
+
CHUNK /[^"\\]+/
|
|
170
|
+
end
|
|
171
|
+
on '"' { push_state :STRING; emit :STRING_BEGIN }
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Each state tries its rules at the current position with an internal `\A` anchor.
|
|
176
|
+
The longest lexeme wins; declaration order breaks equal-length ties. A named
|
|
177
|
+
rule emits its declared terminal and uses the lexeme as its value. Its optional
|
|
178
|
+
action may return a converted value or call `emit TOKEN, value`. `skip` consumes
|
|
179
|
+
without emitting; `on` must call `emit` or `skip`.
|
|
180
|
+
|
|
181
|
+
`state NAME do ... end` creates an exclusive state. Lexer actions call
|
|
182
|
+
`push_state` and `pop_state`; parser actions use the public `lexer_state`
|
|
183
|
+
reader/writer when grammar context changes tokenization. `INITIAL` is reserved,
|
|
184
|
+
states are flat and unique, named rules must reference declared terminals, and
|
|
185
|
+
patterns that match the empty string are rejected.
|
|
186
|
+
|
|
187
|
+
Generated `parse(source, file: "(input)")` accepts String, IO, or Fiber input.
|
|
188
|
+
IO/Fiber chunks can end inside tokens. Locations use one-based grapheme
|
|
189
|
+
`column`/`grapheme_column`, explicit byte columns, and half-open
|
|
190
|
+
`start_byte`/`end_byte` offsets. Unicode property escapes use Ruby Regexp
|
|
191
|
+
semantics.
|
|
192
|
+
|
|
193
|
+
Regex execution remains subject to Ruby Regexp complexity. The static lint
|
|
194
|
+
flags common nested-quantifier shapes as `lexer_redos`; `--warnings=all,error`
|
|
195
|
+
promotes the warning to an error. This heuristic is not a proof of safety.
|
|
196
|
+
Applications must still bound untrusted input and review patterns. See the
|
|
197
|
+
[lexer migration guide](lexer-migration.md) and [ADR 0014](decisions/0014-versioned-generated-lexer.md).
|
|
198
|
+
|
|
199
|
+
## Concrete syntax trees
|
|
200
|
+
|
|
201
|
+
With `pragma cst`, every shift and reduction builds immutable pure-syntax
|
|
202
|
+
Green values on a stack parallel to semantic values. Semantic actions execute
|
|
203
|
+
unchanged and never enter syntax children. `parse`, `do_parse`, and `yyparse`
|
|
204
|
+
return the semantic result; generated lexers additionally expose
|
|
205
|
+
`parse_with_syntax(source, file:)`, whose result provides `value`,
|
|
206
|
+
`syntax_root`, and `diagnostics`. The root is always
|
|
207
|
+
`source_file(start-symbol, $eof)`. Lazy `CST::SyntaxNode` and
|
|
208
|
+
`CST::SyntaxToken` wrappers provide parents, offsets, spans, locations,
|
|
209
|
+
pattern matching, and typed `@node` fields.
|
|
210
|
+
|
|
211
|
+
Generated lexers accept `--cst-trivia=leading|balanced|drop`; `attach` is an
|
|
212
|
+
alias for `leading`. Leading ownership places skipped text on the following
|
|
213
|
+
token. Balanced ownership places text through the first newline on the
|
|
214
|
+
preceding token and the remainder on the next token. EOF owns final trivia.
|
|
215
|
+
Drop omits trivia and deliberately disables coordinate and incremental APIs.
|
|
216
|
+
|
|
217
|
+
Lexical failures, yacc recovery, panic discards, and bounded repair retain
|
|
218
|
+
consumed input in error nodes, skipped trivia, or zero-width missing tokens.
|
|
219
|
+
Inspect `diagnostics`, `contains_error?`, `each_error`, and token
|
|
220
|
+
`missing?`/`error?`; an unrecoverable parse is rooted under
|
|
221
|
+
`synthetic_root`. Application exceptions still propagate. Only current
|
|
222
|
+
format-v6 structured CST tables are executable; older CST tables fail before
|
|
223
|
+
token consumption and must be regenerated. See the
|
|
224
|
+
[CST guide](cst.md) and [migration guide](cst-migration.md).
|
|
225
|
+
|
|
226
|
+
## Generated AST nodes and traversal
|
|
227
|
+
|
|
228
|
+
An extended, action-free alternative may end with an explicit node shape:
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
rule
|
|
232
|
+
expression: expression PLUS expression @node Addition(left, operator, right)
|
|
233
|
+
| NUMBER @node Number(value)
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Fields map positionally to normalized RHS values. Their count must match, each
|
|
238
|
+
must be a unique non-keyword Ruby local identifier, and a node name must be a
|
|
239
|
+
Ruby constant identifier. Reusing a node name is allowed only with the same
|
|
240
|
+
ordered fields. `@node` cannot be combined with a trailing or middle semantic
|
|
241
|
+
action; write the explicit action instead when construction is not positional.
|
|
242
|
+
|
|
243
|
+
The generated parser defines the classes under its `AST` module. Ruby 3.2 and
|
|
244
|
+
later use `Data`; Ruby 3.0 and 3.1 use an immutable keyword-Struct compatibility
|
|
245
|
+
implementation with the same readers, `deconstruct`, and `deconstruct_keys`.
|
|
246
|
+
Generated RBS types each field from its grammar symbol and infers a fully
|
|
247
|
+
annotated nonterminal as the union of its node classes.
|
|
248
|
+
|
|
249
|
+
`AST::Visitor#visit` dispatches to `visit_<node>` and recursively visits fields
|
|
250
|
+
by default. `AST::Listener#walk` calls `enter_<node>`, walks fields, and calls
|
|
251
|
+
`exit_<node>`. The generated RBS enumerates every hook, so a consumer can
|
|
252
|
+
subclass either base under Steep without maintaining a parallel node list.
|
|
43
253
|
|
|
44
254
|
## Productions and actions
|
|
45
255
|
|
|
@@ -56,16 +266,111 @@ end
|
|
|
56
266
|
Alternatives use `|`; a trailing semicolon is optional. `= TOKEN` overrides a production's precedence. The `error` terminal
|
|
57
267
|
enables yacc-style recovery.
|
|
58
268
|
|
|
269
|
+
Extended grammars can write `%empty` as the sole RHS item to document an empty alternative. An implicit empty alternative stays
|
|
270
|
+
compatible but produces `implicit_empty` under extended warning analysis.
|
|
271
|
+
|
|
59
272
|
Actions are opaque Ruby between balanced braces. `val` contains RHS values, `result` begins as `val[0]`, and `_values` is a copy
|
|
60
273
|
of the surrounding value stack. With `no_result_var`, the action's evaluated value is used directly. A middle action becomes an
|
|
61
274
|
empty helper production and consumes one value position in the enclosing RHS.
|
|
62
275
|
|
|
276
|
+
When a lexer returns `[token, value, location]`, actions can read the corresponding locations as `@1`, `@2`, and so on. `@$`
|
|
277
|
+
is the current reduction's immutable `Ibex::Runtime::LocationSpan`. A nonempty span covers the first through last located RHS
|
|
278
|
+
entry; an empty production is zero-width at the current lookahead and remains unlocated if no lookahead location was supplied.
|
|
279
|
+
A zero-width span's `end_*` coordinates equal its start even if the lookahead supplies wider end coordinates. A middle action
|
|
280
|
+
follows that empty-production rule, while its numbered locations address the visible left context. Numbered
|
|
281
|
+
references outside the action's value range are generation errors. Location expressions in strings, regular expressions,
|
|
282
|
+
symbols, comments, and heredoc bodies remain literal text, and ordinary Ruby instance variables are unchanged.
|
|
283
|
+
|
|
284
|
+
`Ibex::Location` is an immutable one-based source range with optional half-open byte offsets. Instance `join` and class
|
|
285
|
+
`Ibex::Location.join` return a covering range and reject mixed files. Lexer-owned hashes and objects remain valid. Inside an
|
|
286
|
+
action, `loc(1)` and `loc(:name)` are callable equivalents of numbered and named RHS locations, while `result_loc` returns the
|
|
287
|
+
current synthesized span. Calls outside an action, unknown names, and out-of-range positions fail explicitly. Parsers that use
|
|
288
|
+
only two-element tokens and no location-sensitive action or observer do not allocate a parallel location stack.
|
|
289
|
+
|
|
63
290
|
Action and `inner` backtraces use the original grammar filename and line by default. `--line-convert-all` applies the same mapping
|
|
64
291
|
to `header` and `footer`; `-l` keeps all backtraces on generated-file lines.
|
|
65
292
|
|
|
293
|
+
### Constructor parameters (extended mode)
|
|
294
|
+
|
|
295
|
+
`%param name` adds a required keyword argument to the generated parser constructor. A quoted RBS type is optional:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
%param context "ParserContext"
|
|
299
|
+
%param lexer
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`Parser.new(context: ..., lexer: ...)` stores the objects as `@context` and `@lexer`. Semantic actions receive locals with the
|
|
303
|
+
same names, so the context is available without global state; lexer methods in the `inner` section use the instance variables.
|
|
304
|
+
The generated RBS declares typed instance variables and constructor keywords, using `untyped` when no type is supplied.
|
|
305
|
+
Parameters are root-only, unique Ruby local identifiers, and cannot be Ruby keywords. The generated initializer is prepended,
|
|
306
|
+
so a custom superclass initializer may receive any remaining keyword arguments.
|
|
307
|
+
|
|
308
|
+
### Parameterized rules (extended mode)
|
|
309
|
+
|
|
310
|
+
A parameterized rule is a structural template:
|
|
311
|
+
|
|
312
|
+
```text
|
|
313
|
+
list(X): X:value { result = [value] }
|
|
314
|
+
| list(X) ',' X { result = val[0] + [val[2]] }
|
|
315
|
+
wrapped(X): (X | list(X))?
|
|
316
|
+
numbers: wrapped(list(NUM)):items { result = items }
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The ordered formals are identifiers and calls may be nested. The callee and opening parenthesis must be byte-adjacent.
|
|
320
|
+
Consequently, `list(NUM)` is a call while `ITEM (A | B)` retains its existing meaning as a symbol followed by an EBNF group.
|
|
321
|
+
A named reference or `?`, `*`, or `+` after the closing parenthesis applies to the specialized result.
|
|
322
|
+
|
|
323
|
+
Templates are not standalone nonterminals and cannot be the start symbol. Repeated definitions must use the same ordered
|
|
324
|
+
formals. Duplicate formals, mixed plain/template definitions, terminal collisions, undefined templates, and arity mismatches
|
|
325
|
+
are positioned errors. Formal occurrences are replaced structurally through nested calls, groups, suffixes, and separated
|
|
326
|
+
lists. `X:value` applies `value` to the substituted symbol or call. A formal `= X` precedence override requires that invocation
|
|
327
|
+
to pass one plain symbol for `X`; ordinary precedence overrides are retained unchanged. Named references inside arguments and
|
|
328
|
+
using a formal as a callee are rejected to avoid ambiguous capture.
|
|
329
|
+
|
|
330
|
+
The Normalizer memoizes a specialization before expanding its body, so direct and mutual same-argument recursion reuse one
|
|
331
|
+
internal `$parameter_N` nonterminal. Resumable item and EBNF continuations on an explicit depth-first worklist preserve ordinary
|
|
332
|
+
helper and production order while avoiding dependence on the Ruby stack. The default is
|
|
333
|
+
1,000 distinct specializations; programmatic callers can configure the positive-Integer
|
|
334
|
+
`max_parameter_specializations:`. Argument-changing recursive instantiation is rejected by structural cycle detection rather
|
|
335
|
+
than an arbitrary depth boundary. Specialized productions retain template actions,
|
|
336
|
+
precedence, types, documentation, locations, and include chains. Grammar IR v2 records
|
|
337
|
+
`expansion.parameter {rule, arguments}`; v1 output omits the expansion record.
|
|
338
|
+
|
|
339
|
+
### Inline rules (extended mode)
|
|
340
|
+
|
|
341
|
+
`%inline` directly before a definition marks a reusable phrase for structural substitution:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
%inline atom: NUM:value { result = value }
|
|
345
|
+
| '(' expression ')' { result = val[1] }
|
|
346
|
+
%inline pair(X): X X { result = val }
|
|
347
|
+
expression: pair(atom):values { result = values }
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The marker must be the exact `%inline` directive followed by whitespace; `%` inside Ruby actions remains Ruby. Inline
|
|
351
|
+
definitions work in roots and fragments and may be plain or parameterized. They do not become final Grammar IR symbols or
|
|
352
|
+
productions. Every definition of a name must agree on its inline marking and parameter formals. Inline rules cannot collide
|
|
353
|
+
with terminals or be the start symbol. Direct, mutual, or indirect cycles whose path contains an inline rule are rejected,
|
|
354
|
+
including cycles through ordinary rules, nested EBNF, and parameterized calls.
|
|
355
|
+
|
|
356
|
+
Alternatives are substituted left-to-right before LR construction. Nested or repeated uses form a deterministic cartesian
|
|
357
|
+
product. The caller's explicit precedence override wins; otherwise the rightmost precedence-contributing inline phrase retains
|
|
358
|
+
its explicit override, and flattened terminals retain normal rightmost-terminal precedence. `Ibex::Normalizer` accepts a
|
|
359
|
+
positive-Integer `max_inline_expansions:` limit, defaulting to 10,000 materialized productions. Parameter actuals contribute
|
|
360
|
+
cycle edges only when their callee position is transitively live, and both cycle validation and expansion use heap worklists so
|
|
361
|
+
grammar nesting is independent of the Ruby call stack.
|
|
362
|
+
|
|
363
|
+
Eliminated reductions still run their explicit or implicit actions in logical post-order. Named references, `val`, `_values`,
|
|
364
|
+
`@N`, `@$`, empty spans, middle actions, `result`/`no_result_var`, and parser instance methods retain their logical rule view.
|
|
365
|
+
Grammar IR v2 serializes the executable sequence in `action.composition.plan` and records
|
|
366
|
+
`expansion.inline {rule}`; dump/load followed by code generation preserves it. `yyaccept` and `yyerror` stop the remaining
|
|
367
|
+
logical fragments and caller after the current fragment completes, and `yyerrok` does not erase that `yyerror`. Version 1 omits
|
|
368
|
+
both metadata families.
|
|
369
|
+
|
|
66
370
|
The action scanner handles nested braces, quoted/backtick strings and interpolation, `%q/%Q/%w/%W/%i/%I/%x/%r/%s`, regular
|
|
67
371
|
expressions, comments, character literals, and unquoted, single-quoted, double-quoted, or backtick heredocs. Indented, squiggly,
|
|
68
|
-
interpolated, and multiple heredocs on one opener line are supported.
|
|
372
|
+
interpolated, and multiple heredocs on one opener line are supported. Heredoc terminators follow their indentation mode, and
|
|
373
|
+
multiple openers on one line are consumed in source order.
|
|
69
374
|
|
|
70
375
|
## Runtime errors
|
|
71
376
|
|
|
@@ -73,11 +378,69 @@ The default `on_error(token_id, value, value_stack)` raises `Ibex::ParseError`.
|
|
|
73
378
|
production to recover. Unknown external token objects receive a temporary negative internal id, remain printable through
|
|
74
379
|
`token_to_str`, and always invoke `on_error` before recovery is attempted.
|
|
75
380
|
|
|
76
|
-
|
|
381
|
+
With `%recover sync:`, a returned `on_error` first permits ordinary yacc `error`-token recovery. Only when no stack state can
|
|
382
|
+
shift `error` does panic-mode synchronization begin. Discarded application tokens call `on_discard` with reason `recovery`;
|
|
383
|
+
the selected synchronization token is not discarded. `on_error_recover` and the `recover` event fire once after a stack state
|
|
384
|
+
that accepts the synchronization token is found. EOF before a usable synchronization point rejects the parse.
|
|
385
|
+
|
|
386
|
+
Optional observer methods default to no-ops. `on_shift(token_id, value, state)` follows each ordinary input-token shift;
|
|
77
387
|
`on_reduce(production_id, values, result)` follows a completed semantic action and goto; and
|
|
78
388
|
`on_error_recover(token_id, value, value_stack)` follows a successful synthetic `error` shift while retaining the original
|
|
79
|
-
unexpected-token context.
|
|
80
|
-
|
|
389
|
+
unexpected-token context. Their `on_shift_location`, `on_reduce_location`, and `on_error_recover_location` companions add
|
|
390
|
+
locations while preserving the original hook signatures. `on_discard(token_id, value, location, reason)` reports an
|
|
391
|
+
application token removed by yacc recovery. Hook return values are ignored and exceptions propagate. `trace_value_printer=`
|
|
392
|
+
opts a parser into value rendering in `yydebug`; without it, traces never expose semantic values. Extended grammars may define
|
|
393
|
+
symbol-specific `%printer SYMBOL { Ruby expression }` formatters. Their `value` local has the symbol's declared semantic type,
|
|
394
|
+
and declared `%param` locals are also available. A programmatic printer overrides generated symbol formatters. Neither form is
|
|
395
|
+
called unless `yydebug` is true, and formatter failures are rendered by exception class without inspecting the value. See
|
|
396
|
+
[ADR 0010](decisions/0010-committed-runtime-observation.md) for the observation boundary.
|
|
397
|
+
|
|
398
|
+
For external tooling, `observe { |event| ... }` registers an ordered observer and returns an opaque subscription accepted by
|
|
399
|
+
`unobserve`. Events are immutable, sequence-numbered per parse session, and cover `start`, `shift`, `reduce`, `error`, `recover`,
|
|
400
|
+
`discard`, `accept`, and `reject`. Semantic values and locations are bounded JSON summaries rather than live objects.
|
|
401
|
+
`Ibex::Runtime::EventJSONLTracer.attach(parser, io:)` writes the versioned schema at
|
|
402
|
+
`schema/runtime-event-v1.schema.json`; write and serialization failures propagate. This API is separate from the legacy
|
|
403
|
+
hook-shaped `Runtime::JSONLTracer`. See [ADR 0010](decisions/0010-committed-runtime-observation.md).
|
|
404
|
+
|
|
405
|
+
Every parser instance owns immutable `Runtime::ResourceLimits`. The defaults allow a 10,000-entry LR state stack and 100
|
|
406
|
+
recovery entries per parse. Pass `resource_limits:` to the generated parser constructor or replace it while the instance is
|
|
407
|
+
idle. Stack shifts/gotos and recovery entries that exceed their budget raise `Ibex::ResourceLimitError` with structured
|
|
408
|
+
resource, limit, observed value, state, and location data. Concurrent parses share immutable generated tables but must use
|
|
409
|
+
distinct parser instances.
|
|
410
|
+
|
|
411
|
+
Assign an immutable `Ibex::Runtime::RepairPolicy` before parsing to opt into bounded insertion, deletion, and replacement search.
|
|
412
|
+
The default costs are 1/1/2 with maximum cost 3, 5,000 configurations/table actions, eight lookahead records, three successful
|
|
413
|
+
shifts, and a 256-state simulated stack. `on_repair(plan)` observes the selected immutable edits after the one `on_error` call and
|
|
414
|
+
before normal action replay. Insertions carry nil values, replacements retain the original value/location, and search failure
|
|
415
|
+
continues into yacc recovery without reporting the incident twice. Push parsing may return `:need_more` while retaining the
|
|
416
|
+
unexpected token. Semantic `yyerror` is not automatically repaired. See [ADR
|
|
417
|
+
0012](decisions/0012-bounded-nonexecuting-analysis.md).
|
|
418
|
+
The gallery JSON [error UX evidence](error-ux.md) records the SP-4 baseline:
|
|
419
|
+
8 of 10 selected plans were assessed useful, so the bounded single-plan feature
|
|
420
|
+
continues as an explicit experimental option.
|
|
421
|
+
|
|
422
|
+
The versioned stream can be converted to grammar-test coverage with `ibex coverage collect EVENTS.jsonl`, combined across
|
|
423
|
+
processes with `coverage merge`, and gated with `coverage check --min-states=PERCENT --min-productions=PERCENT`. State coverage
|
|
424
|
+
counts the initial state plus committed shift, reduce-goto, and recovery destinations; production coverage counts committed
|
|
425
|
+
reductions. Complete sessions and generated-parser metadata are required. Reports follow
|
|
426
|
+
`schema/runtime-coverage-v1.schema.json`; see [ADR 0010](decisions/0010-committed-runtime-observation.md).
|
|
427
|
+
|
|
428
|
+
`ibex debug AUTOMATON.json [TOKEN...]` simulates shifts, reductions, gotos, accept, and error directly from validated Automaton
|
|
429
|
+
IR. It never executes actions. When tokens are omitted, supply one terminal name or unique display name per stdin line; a blank
|
|
430
|
+
line or EOF finishes the input. Use `--format=json` for `schema/table-simulation-v1.schema.json`, and bound pathological tables
|
|
431
|
+
with positive `--max-steps` and `--max-stack`. See [ADR 0012](decisions/0012-bounded-nonexecuting-analysis.md).
|
|
432
|
+
|
|
433
|
+
## Grammar-declared tests
|
|
434
|
+
|
|
435
|
+
`ibex test [--mode=MODE] [--algorithm=NAME] [--entry-isolation] [--timeout=SECONDS] grammar.y` executes every `%test` in source
|
|
436
|
+
order. The generated parser class must be constructible without arguments and define `parse(source)`. Each case uses a fresh
|
|
437
|
+
instance. A normal return counts as acceptance, `Ibex::Runtime::ParseError` counts as rejection, and lexer/application
|
|
438
|
+
exceptions are test errors rather than syntax rejections. Empty suites and grammars with required `%param` declarations fail
|
|
439
|
+
explicitly.
|
|
440
|
+
|
|
441
|
+
The complete suite runs in an isolated Ruby child process with a ten-second default timeout. Generated footer guards remain
|
|
442
|
+
false because a separate runner loads the parser file. Output is TAP-like and the command exits nonzero on any mismatch,
|
|
443
|
+
exception, timeout, or invalid child result. This is process isolation for reliable tooling, not a sandbox for untrusted code.
|
|
81
444
|
|
|
82
445
|
## Extended EBNF and names
|
|
83
446
|
|
|
@@ -93,8 +456,8 @@ Extended mode supports:
|
|
|
93
456
|
Parenthesized groups may contain sequences, alternatives, and nested EBNF, for example `(KEY VALUE)*`, `(A | B)+`, or
|
|
94
457
|
`separated_list((KEY VALUE), ',')`. A one-item group has that item's value; a multi-item group has an Array of its item values;
|
|
95
458
|
an empty group has `nil`. Named references must be unique in an outer alternative and cannot use `result`, `val`, or `_values`;
|
|
96
|
-
references inside a group are rejected because the group is lowered behind one outer value slot. Text, DOT, and HTML
|
|
97
|
-
render lowered helper nonterminals as their original EBNF expressions instead of exposing generated helper names.
|
|
459
|
+
references inside a group are rejected because the group is lowered behind one outer value slot. Text, DOT, Mermaid, and HTML
|
|
460
|
+
reports render lowered helper nonterminals as their original EBNF expressions instead of exposing generated helper names.
|
|
98
461
|
|
|
99
462
|
Actions and named references are supported on an outer production alternative, but not inside a parenthesized EBNF group.
|
|
100
463
|
Move the action or binding to a separately named ordinary rule and reference that rule from the group.
|
|
@@ -102,9 +465,86 @@ Move the action or binding to a separately named ordinary rule and reference tha
|
|
|
102
465
|
## Strict diagnostics
|
|
103
466
|
|
|
104
467
|
Grammar IR retains structured diagnostics for undeclared or unused terminals, unreachable nonterminals, duplicate productions,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
suppresses them.
|
|
468
|
+
unused precedence declarations, explicitly declared terminals used only by unreachable rules, and a start symbol that cannot
|
|
469
|
+
derive any terminal sentence. They remain silent by default for compatibility. `--warnings=all` prints them,
|
|
470
|
+
`--warnings=all,error` or `--warnings=error` promotes them to command failures, and `--warnings=none` explicitly suppresses them.
|
|
471
|
+
An unexpected LALR conflict also gets an advisory `--algorithm=ielr` note when IELR removes at least one unresolved
|
|
472
|
+
conflict; this note does not change generation or exit status.
|
|
473
|
+
|
|
474
|
+
`ibex check --ambiguity grammar.y` searches every parser conflict for a complete sentence accepted through two interpretations.
|
|
475
|
+
`--max-tokens` and `--max-configurations` bound the search per conflict; `--algorithm=lr1` excludes conflicts introduced only by
|
|
476
|
+
LALR merging. Exit status 1 means a concrete ambiguity was found, 2 means a configuration budget was exhausted, and 0 means no
|
|
477
|
+
ambiguity was found within the declared bounds. The last result is not a general proof of unambiguity. `--format=json` emits
|
|
478
|
+
the versioned check result and explored counts.
|
|
479
|
+
|
|
480
|
+
When an empty helper created for a middle action participates in a conflict, Automaton IR, text reports, HTML, and
|
|
481
|
+
`ibex explain` retain the action's source location as `midrule_origins`. This makes the otherwise synthetic reduction traceable
|
|
482
|
+
to the grammar expression that introduced it.
|
|
483
|
+
|
|
484
|
+
## Transactional generation and watch mode
|
|
485
|
+
|
|
486
|
+
Ruby generation renders all requested files before replacing any target. Existing targets keep their modes, `--executable`
|
|
487
|
+
selects an executable parser mode, and generated paths that alias an input, have multiple hard links, or collide by portable
|
|
488
|
+
case/Unicode spelling are rejected. Companion outputs publish before the parser.
|
|
489
|
+
|
|
490
|
+
`--manifest[=FILE]` opts into a version-1 JSON manifest; without `=FILE`, `parser.rb` uses `parser.ibex.json`. The manifest is
|
|
491
|
+
published last and records the exact canonical root, fragments, IR input, and message bytes consumed, relevant generation
|
|
492
|
+
options, and every other artifact's path, size, and SHA-256 digest. `--check --manifest` compares all requested output bytes and
|
|
493
|
+
the manifest without rewriting them. `Ibex::GenerationManifest.validate_file(path)` validates the document and its current
|
|
494
|
+
artifact bytes. For coherent concurrent reads, read the manifest, verify every entry, and retry from a newly read manifest if
|
|
495
|
+
anything is missing or mismatched.
|
|
496
|
+
|
|
497
|
+
`--watch` repeatedly applies the same transaction to Ruby file generation. It observes the root, the latest successful include
|
|
498
|
+
closure, unresolved include attempts, an optional messages file, and repairable output paths. Failed candidates leave the last
|
|
499
|
+
successful generation intact; an unchanged failure is reported once. Source changes during render or publication retry after
|
|
500
|
+
debouncing. Watch mode requires a grammar file and cannot be combined with stdin, `--from`, `--check`, or `--check-only`.
|
|
501
|
+
`SIGINT` and `SIGTERM` exit with status 130 and 143. Rake tasks are timestamp-based and reject `--watch`.
|
|
502
|
+
|
|
503
|
+
## Example-keyed error messages
|
|
504
|
+
|
|
505
|
+
`ibex errors --list grammar.y` prints a deterministic `ibex-messages v2` template without writing a file. Each entry is keyed by
|
|
506
|
+
a shortest token sentence that reaches a syntax error, rather than by an unstable automaton state number:
|
|
507
|
+
|
|
508
|
+
```text
|
|
509
|
+
# ibex-messages v2
|
|
510
|
+
sentence: NUM '+' ')'
|
|
511
|
+
## E0042
|
|
512
|
+
# entry: expression
|
|
513
|
+
# state: 7
|
|
514
|
+
# expected: '(', NUM
|
|
515
|
+
| An operand or opening parenthesis is required before ')'.
|
|
516
|
+
end
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
The `# state:` and `# expected:` lines are review hints; the sentence and error ID are the durable keys. Message lines start with
|
|
520
|
+
`|`; multiple lines are joined with newlines, and `\\`, `\n`, `\t`, and `\r` are the supported escapes. Blank lines and comments
|
|
521
|
+
are ignored.
|
|
522
|
+
|
|
523
|
+
`ibex errors --update grammar.y` atomically writes `grammar.messages`; use `--update=FILE`, `--algorithm=NAME`, or an IR `--from`
|
|
524
|
+
option to select another destination or automaton. It keeps IDs and message bodies, and reports three review classes on stdout:
|
|
525
|
+
`unreachable` when a saved sentence is no longer an error, `uncovered` for a new error state, and `moved` when a sentence now
|
|
526
|
+
reaches a different state. An existing v1 numeric-state file is accepted and migrated on update. Use `--max-tokens=N` and
|
|
527
|
+
`--max-configurations=N` to bound the shortest-sentence search; both default to the counterexample search limits.
|
|
528
|
+
|
|
529
|
+
Pass the reviewed file to Ruby generation with `--messages=grammar.messages`. Each matching message replaces only the generic
|
|
530
|
+
syntax-error sentence. `ParseError#error_id` exposes its stable `E00xx` identifier, and structured token, location,
|
|
531
|
+
expected-token, suggestion, source-line, and caret data remain available. A saved active sentence that no longer reaches an
|
|
532
|
+
error is rejected with an instruction to run the updater.
|
|
533
|
+
|
|
534
|
+
## Analysis and visualizations
|
|
535
|
+
|
|
536
|
+
`--emit=sets` writes deterministic JSON containing nullable nonterminals and their FIRST and FOLLOW sets. `--dot=FILE` and
|
|
537
|
+
`--mermaid=FILE` write automaton graphs. `--html=FILE` writes a self-contained report with state search, conflict highlighting,
|
|
538
|
+
and a filter that keeps a selected conflict state and its one-hop neighbors. All three visualizations can be produced while
|
|
539
|
+
generating Ruby or when resuming from Automaton IR. `--railroad=FILE` writes a self-contained SVG railroad diagram from normalized
|
|
540
|
+
Grammar IR, so it is also available before automaton construction and when resuming from Grammar or Automaton IR.
|
|
541
|
+
|
|
542
|
+
`ibex explain grammar.y` is the focused conflict view. `--state=N` and `--token=NAME` select their intersection;
|
|
543
|
+
`--format=text|json` chooses step-by-step text or the version-1 document described by `schema/explain-v1.schema.json`.
|
|
544
|
+
`--algorithm=slr|lalr|ielr|lr1` selects construction, `--mode=default|extended` applies the same frontend mode as generation, and both
|
|
545
|
+
counterexample budget options bound its witness search. Search runs only after state and token selection and only for matching
|
|
546
|
+
conflicts. Token selectors prefer a canonical grammar name, then an exact unique display name. Unknown or ambiguous selectors
|
|
547
|
+
are errors; valid selectors with no matching conflict succeed with an empty result.
|
|
108
548
|
|
|
109
549
|
## Ruby DSL
|
|
110
550
|
|
|
@@ -123,5 +563,5 @@ end
|
|
|
123
563
|
grammar_ir = Ibex::Normalizer.new(ast).normalize
|
|
124
564
|
```
|
|
125
565
|
|
|
126
|
-
The builder also provides `options`, `expect`, `start`, `convert`, `user_code`, `ref(as:)`, `optional`,
|
|
127
|
-
`separated_list`, and `inline`.
|
|
566
|
+
The builder also provides `options`, `expect`, `start`, `convert`, `display`, `type`, `user_code`, `ref(as:)`, `optional`,
|
|
567
|
+
`star`, `plus`, `separated_list`, and `inline`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Migrating a handwritten lexer
|
|
2
|
+
|
|
3
|
+
The generated lexer uses the same `next_token` contract as a handwritten pull
|
|
4
|
+
lexer, so migration can stay inside the grammar file.
|
|
5
|
+
|
|
6
|
+
Given a handwritten scanner:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
def next_token
|
|
10
|
+
@scanner.skip(/\s+/)
|
|
11
|
+
return false if @scanner.eos?
|
|
12
|
+
return [:NUMBER, Integer(text, 10)] if (text = @scanner.scan(/\d+/))
|
|
13
|
+
return [punctuation, nil] if (punctuation = @scanner.scan(/[()+]/))
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
declare the equivalent rules:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
pragma extended
|
|
21
|
+
token NUMBER
|
|
22
|
+
lexer
|
|
23
|
+
skip /\s+/
|
|
24
|
+
NUMBER /\d+/ { |text| Integer(text, 10) }
|
|
25
|
+
on /[()+]/ { |text| emit text, nil }
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then remove the source/scanner initialization and call generated
|
|
30
|
+
`parse(source, file: ...)`. Existing semantic actions, token names, values,
|
|
31
|
+
parser hooks, and location consumers do not change. A custom `parse` wrapper
|
|
32
|
+
may retain an application-specific default filename:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
def parse(source, file: "(expression)") = super
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Map scanner modes to `state NAME do ... end`, transitions to `push_state` and
|
|
39
|
+
`pop_state`, and parser-controlled modes to `self.lexer_state = :NAME`.
|
|
40
|
+
Patterns are tried only in the active state.
|
|
41
|
+
|
|
42
|
+
Before deleting a handwritten lexer, compare its token triples with
|
|
43
|
+
`parser.lex(source).next_token`, especially at newlines and multibyte text.
|
|
44
|
+
Generated locations expose grapheme and byte columns plus half-open byte
|
|
45
|
+
offsets. String, IO, and Fiber sources share the same output even when chunks
|
|
46
|
+
split a token.
|
|
47
|
+
|
|
48
|
+
The generator rejects empty matches and invalid regex syntax. Its
|
|
49
|
+
`lexer_redos` lint only detects common risky nested quantifiers; it cannot make
|
|
50
|
+
arbitrary Ruby regular expressions safe. Review patterns and bound untrusted
|
|
51
|
+
input.
|