Skip to content

Commit 30d92ac

Browse files
committed
Add --no-inline option support to convert inline rules to regular non-terminal rules
1 parent fdb2d74 commit 30d92ac

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

lib/lrama/grammar.rb

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ def epilogue=(epilogue)
256256

257257
# @rbs () -> void
258258
def prepare
259-
unless @no_inline
259+
if @no_inline
260+
convert_inline_rules_to_regular_rules
261+
else
260262
validate_inline_rules
261263
resolve_inline_rules
262264
end
@@ -462,6 +464,40 @@ def resolve_inline_rules
462464
end
463465
end
464466

467+
# Convert inline rules to regular rules when --no-inline is specified.
468+
# This allows inline rules to be treated as ordinary non-terminal rules.
469+
# Only non-parameterized inline rules can be converted (rules with parameters
470+
# require instantiation and cannot be converted to simple rules).
471+
#
472+
# @rbs () -> void
473+
def convert_inline_rules_to_regular_rules
474+
inline_rules = @parameterized_resolver.rules.select { |rule| rule.inline? && rule.required_parameters_count == 0 }
475+
return if inline_rules.empty?
476+
477+
converted_names = Set.new #: Set[String]
478+
479+
inline_rules.each do |inline_rule|
480+
lhs_token = Lexer::Token::Ident.new(s_value: inline_rule.name)
481+
converted_names << inline_rule.name
482+
483+
inline_rule.rhs.each do |rhs|
484+
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterized_resolver, lhs_tag: inline_rule.tag)
485+
rule_builder.lhs = lhs_token
486+
rhs.symbols.each { |sym| rule_builder.add_rhs(sym) }
487+
rule_builder.precedence_sym = rhs.precedence_sym
488+
rule_builder.user_code = rhs.user_code
489+
rule_builder.complete_input
490+
@rule_builders << rule_builder
491+
end
492+
end
493+
494+
# Remove converted inline rules from the resolver so that find_inline
495+
# won't find them, allowing rules that reference them to be built normally
496+
@parameterized_resolver.rules = @parameterized_resolver.rules.reject do |rule|
497+
rule.inline? && converted_names.include?(rule.name)
498+
end
499+
end
500+
465501
# @rbs () -> void
466502
def normalize_rules
467503
add_accept_rule

sig/generated/lrama/grammar.rbs

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/lrama/parser_spec.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3312,6 +3312,35 @@
33123312
expect(times_rule.token_code.s_value).to include("$$ = _inline_2($1, $3)")
33133313
end
33143314
end
3315+
3316+
context 'when no_inline is true' do
3317+
let(:path) { "inlining/basic.y" }
3318+
let(:grammar) do
3319+
grammar = Lrama::Parser.new(y, path).parse
3320+
stdlib_grammar = Lrama::Parser.new(File.read("lib/lrama/grammar/stdlib.y"), "lib/lrama/grammar/stdlib.y").parse
3321+
grammar.prepend_parameterized_rules(stdlib_grammar.parameterized_rules)
3322+
grammar.no_inline = true
3323+
grammar.prepare
3324+
grammar.validate!
3325+
grammar
3326+
end
3327+
3328+
it "treats inline rules as regular non-terminal rules" do
3329+
nterm_names = grammar.nterms.map { |n| n.id.s_value }
3330+
expect(nterm_names).to include("op")
3331+
expect(nterm_names).to include("other_op")
3332+
3333+
expression_rules = grammar.rules.select { |r| r.lhs.id.s_value == "expression" }
3334+
op_referenced = expression_rules.any? do |r|
3335+
r.rhs.any? { |s| s.id.s_value == "op" }
3336+
end
3337+
other_op_referenced = expression_rules.any? do |r|
3338+
r.rhs.any? { |s| s.id.s_value == "other_op" }
3339+
end
3340+
expect(op_referenced).to be true
3341+
expect(other_op_referenced).to be true
3342+
end
3343+
end
33153344
end
33163345

33173346
it "; for rules is optional" do

0 commit comments

Comments
 (0)