diff --git a/lib/lrama/grammar.rb b/lib/lrama/grammar.rb index 52625627..40696c05 100644 --- a/lib/lrama/grammar.rb +++ b/lib/lrama/grammar.rb @@ -7,6 +7,7 @@ require_relative "grammar/counter" require_relative "grammar/destructor" require_relative "grammar/error_token" +require_relative "grammar/inlined_rule_builder" require_relative "grammar/parameterizing_rule" require_relative "grammar/percent_code" require_relative "grammar/precedence" @@ -294,10 +295,10 @@ def append_special_symbols end def resolve_inline_rules - while @rule_builders.any? {|r| r.has_inline_rules? } do + while @rule_builders.any?(&:has_inline_rules?) do @rule_builders = @rule_builders.flat_map do |builder| if builder.has_inline_rules? - builder.resolve_inline_rules + builder.build_inlined_builders else builder end diff --git a/lib/lrama/grammar/binding.rb b/lib/lrama/grammar/binding.rb index 5e6e7c59..23df18cb 100644 --- a/lib/lrama/grammar/binding.rb +++ b/lib/lrama/grammar/binding.rb @@ -22,6 +22,10 @@ def resolve_symbol(symbol) end end + def concatenated_args_str(token) + "#{token.rule_name}_#{token_to_args_s_values(token).join('_')}" + end + private def parameter_to_arg(symbol) @@ -30,6 +34,17 @@ def parameter_to_arg(symbol) end arg end + + def token_to_args_s_values(token) + token.args.map do |arg| + resolved = resolve_symbol(arg) + if resolved.is_a?(Lexer::Token::InstantiateRule) + [resolved.s_value, resolved.args.map(&:s_value)] + else + resolved.s_value + end + end + end end end end diff --git a/lib/lrama/grammar/inlined_rule_builder.rb b/lib/lrama/grammar/inlined_rule_builder.rb new file mode 100644 index 00000000..a2d58496 --- /dev/null +++ b/lib/lrama/grammar/inlined_rule_builder.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module Lrama + class Grammar + class InlinedRuleBuilder + def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolver, lhs, lhs_tag, rhs, user_code, precedence_sym, line) + @rule_counter = rule_counter + @midrule_action_counter = midrule_action_counter + @parameterizing_rule_resolver = parameterizing_rule_resolver + @lhs = lhs + @lhs_tag = lhs_tag + @rhs = rhs + @user_code = user_code + @precedence_sym = precedence_sym + @line = line + end + + def build_builders(rule, token, index) + binding = create_binding(rule, token) + + rule.rhs_list.map do |rhs| + rule_builder = initialize_builder + resolve_rhs(rule_builder, rhs, index, binding) + finalize_builder(rule_builder, rhs, index) + rule_builder + end + end + + private + + def initialize_builder + RuleBuilder.new( + @rule_counter, + @midrule_action_counter, + @parameterizing_rule_resolver, + lhs_tag: @lhs_tag + ) + end + + def create_binding(rule, token) + return unless token.is_a?(Lexer::Token::InstantiateRule) + + Binding.new(rule, token.args) + end + + def resolve_rhs(rule_builder, rhs, index, bindings) + @rhs.each_with_index do |token, i| + if index == i + rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) } + else + rule_builder.add_rhs(token) + end + end + end + + def finalize_builder(rule_builder, rhs, index) + rule_builder.lhs = @lhs + rule_builder.line = @line + rule_builder.precedence_sym = @precedence_sym + rule_builder.user_code = replace_user_code(rhs, index) + end + + def replace_user_code(inline_rhs, index) + return @user_code if inline_rhs.user_code.nil? + return @user_code if @user_code.nil? + + code = @user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value) + @user_code.references.each do |ref| + next if ref.index.nil? || ref.index <= index # nil is a case for `$$` + code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}") + code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}") + end + Lrama::Lexer::Token::UserCode.new(s_value: code, location: @user_code.location) + end + end + end +end diff --git a/lib/lrama/grammar/rule_builder.rb b/lib/lrama/grammar/rule_builder.rb index 06097eb7..243a8420 100644 --- a/lib/lrama/grammar/rule_builder.rb +++ b/lib/lrama/grammar/rule_builder.rb @@ -66,27 +66,14 @@ def has_inline_rules? rhs.any? { |token| @parameterizing_rule_resolver.find_inline(token) } end - def resolve_inline_rules - resolved_builders = [] #: Array[RuleBuilder] + def build_inlined_builders + inlined_rule_builder = Lrama::Grammar::InlinedRuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, @lhs, @lhs_tag, @rhs, @user_code, @precedence_sym, @line) rhs.each_with_index do |token, i| - if (inline_rule = @parameterizing_rule_resolver.find_inline(token)) - inline_rule.rhs_list.each do |inline_rhs| - rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: lhs_tag) - if token.is_a?(Lexer::Token::InstantiateRule) - resolve_inline_rhs(rule_builder, inline_rhs, i, Binding.new(inline_rule, token.args)) - else - resolve_inline_rhs(rule_builder, inline_rhs, i) - end - rule_builder.lhs = lhs - rule_builder.line = line - rule_builder.precedence_sym = precedence_sym - rule_builder.user_code = replace_inline_user_code(inline_rhs, i) - resolved_builders << rule_builder - end - break - end + next unless (inline_rule = @parameterizing_rule_resolver.find_inline(token)) + + return inlined_rule_builder.build_builders(inline_rule, token, i) end - resolved_builders + [] end private @@ -136,7 +123,7 @@ def process_rhs raise "Unexpected token. #{token}" unless parameterizing_rule bindings = Binding.new(parameterizing_rule, token.args) - lhs_s_value = lhs_s_value(token, bindings) + lhs_s_value = bindings.concatenated_args_str(token) if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value)) @replaced_rhs << created_lhs else @@ -174,41 +161,6 @@ def process_rhs end end - def lhs_s_value(token, bindings) - s_values = token.args.map do |arg| - resolved = bindings.resolve_symbol(arg) - if resolved.is_a?(Lexer::Token::InstantiateRule) - [resolved.s_value, resolved.args.map(&:s_value)] - else - resolved.s_value - end - end - "#{token.rule_name}_#{s_values.join('_')}" - end - - def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil) - rhs.each_with_index do |token, i| - if index == i - inline_rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) } - else - rule_builder.add_rhs(token) - end - end - end - - def replace_inline_user_code(inline_rhs, index) - return user_code if inline_rhs.user_code.nil? - return user_code if user_code.nil? - - code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value) - user_code.references.each do |ref| - next if ref.index.nil? || ref.index <= index # nil is a case for `$$` - code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}") - code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}") - end - Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location) - end - def numberize_references # Bison n'th component is 1-origin (rhs + [user_code]).compact.each.with_index(1) do |token, i|