diff --git a/lib/lrama/grammar/symbol.rb b/lib/lrama/grammar/symbol.rb index f9dffcad..cb230b42 100644 --- a/lib/lrama/grammar/symbol.rb +++ b/lib/lrama/grammar/symbol.rb @@ -100,6 +100,10 @@ def comment alias_name || id.s_value end end + + def inspect + id.s_value + end end end end diff --git a/lib/lrama/state.rb b/lib/lrama/state.rb index 6688da01..938afe7b 100644 --- a/lib/lrama/state.rb +++ b/lib/lrama/state.rb @@ -10,8 +10,8 @@ module Lrama class State attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts, - :default_reduction_rule, :closure, :items, :predecessors - attr_accessor :shifts, :reduces, :lalr_isocore + :default_reduction_rule, :closure, :items + attr_accessor :shifts, :reduces, :ielr_isocores def initialize(id, accessing_symbol, kernels) @id = id @@ -25,7 +25,11 @@ def initialize(id, accessing_symbol, kernels) @resolved_conflicts = [] @default_reduction_rule = nil @predecessors = [] + @internal_dependencies = {} + @successor_dependencies = {} + @predecessor_dependencies = {} @lalr_isocore = self + @ielr_isocores = [self] end def closure=(closure) @@ -151,15 +155,65 @@ def rr_conflicts end end - def always_follows(shift, next_state) - (internal_dependencies[[shift, next_state]] + successor_dependencies[[shift, next_state]]) - .map {|st, _, _| st.term_transitions }.flat_map {|sh, _| sh.next_sym }.uniq - end - def inspect "#{id} -> #{@kernels.map(&:to_s).join(', ')}" end + def lookaheads_recomputed + !@item_lookahead_set.nil? + end + + def propagate_lookaheads(next_state) + next_state.kernels.to_h {|item| + lookahead_sets = + if item.position == 1 + goto_follow_set(item.lhs) + else + kernel = kernels.find {|k| k.predecessor_item_of?(item) } + item_lookahead_set[kernel] + end + # pp next_state.lookahead_set_filters[item] + + [item, lookahead_sets & next_state.lookahead_set_filters[item]] + } + end + + def compatible_lookahead?(filtered_lookahead) + lookaheads_recomputed || + @lalr_isocore.annotation_list.all? {|token, actions| + a = dominant_contribution(token, actions, item_lookahead_set) + b = dominant_contribution(token, actions, filtered_lookahead) + a.nil? || b.nil? || a == b + } + end + + def lookahead_set_filters + kernels.to_h {|kernel| + # pp @lalr_isocore.annotation_list.transform_keys(&:inspect) + [kernel, + @lalr_isocore.annotation_list.filter_map {|token, actions| + filter = token.term? && actions.any? {|action, contributions| + !contributions.nil? && contributions.key?(kernel) && contributions[kernel] + } + filter && token + }] + } + end + + def dominant_contribution(token, actions, lookaheads) + a = actions.filter_map {|action, contributions| + action if contributions.nil? || contributions.any? {|item, contributed| contributed && lookaheads[item].include?(token) } + } + return nil if a.empty? + a.reject {|action| + if action.is_a?(State::Shift) + action.not_selected + elsif action.is_a?(State::Reduce) + action.not_selected_symbols.include?(token) + end + } + end + def inadequacy_list return @inadequacy_list if @inadequacy_list @@ -180,103 +234,113 @@ def inadequacy_list def annotation_list manifestations = annotate_manifestation - predecessors = transitions.map {|_, next_state| annotate_predecessor(next_state.annotation_list) } - manifestations + predecessors + predecessors = transitions.map {|_, next_state| next_state.annotate_predecessor(self) } + # pp self, manifestations, predecessors + predecessors.reduce(manifestations) {|result, annotations| + result.merge(annotations) {|_, actions_a, actions_b| + if actions_a.nil? || actions_b.nil? + actions_a || actions_b + else + actions_a.merge(actions_b) {|_, contributions_a, contributions_b| + contributions_a.merge(contributions_b) {|_, contributed_a, contributed_b| + contributed_a || contributed_b + } + } + end + } + } end def annotate_manifestation - pp inadequacy_list.transform_keys { _1.id.s_value }.transform_values { _1.map(&:to_s) } - inadequacy_list.map {|token, actions| - actions.map {|action| + inadequacy_list.transform_values {|actions| + actions.to_h {|action| if action.is_a?(Shift) - [InadequacyAnnotation.new(token: token, action: action, item: nil, contributed: false)] + [action, nil] elsif action.is_a?(Reduce) if action.rule.empty_rule? - lhs_contributions(action.rule.lhs, token).map {|kernel, contributed| - InadequacyAnnotation.new(token: token, action: action, item: kernel, contributed: contributed) - } + [action, lhs_contributions(action.rule.lhs, inadequacy_list.key(actions))] else - kernels.map {|kernel| - contributed = kernel.rule == action.rule && kernel.end_of_rule? - InadequacyAnnotation.new(token: token, action: action, item: kernel, contributed: contributed) - } + contributions = kernels.to_h {|kernel| [kernel, kernel.rule == action.rule && kernel.end_of_rule?] } + [action, contributions] end end } } end - def annotate_predecessor(annotation_list) - annotation_list.reduce([]) {|annotation| - next [token, {}] if annotation.no_contributions? || actions.any? {|action, hash| - p action, hash - hash.keys.any? {|item| hash[item] && item.position == 1 && compute_lhs_contributions(state, item.lhs, token).empty? } - } - [ - token, actions.to_h {|action, hash| - [ - action, hash.to_h {|item, _| - kernel = state.kernels.find {|k| k.rule == item.rule && k.position == item.position - 1 } - [kernel, - hash[item] && - ( - !kernel.nil? && (state.item_lookahead_set[kernel].include?(token)) || - (item.position == 1 && compute_lhs_contributions(state, item.lhs, token)[item]) - ) - ] - } - ] + def annotate_predecessor(predecessor) + annotation_list.transform_values {|actions| + token = annotation_list.key(actions) + actions.transform_values {|inadequacy| + next nil if inadequacy.nil? + lhs_adequacy = kernels.all? {|kernel| + inadequacy[kernel] && kernel.position == 1 && predecessor.lhs_contributions(kernel.lhs, token).nil? } - ] - } - end - - def item_lookahead_set - @item_lookahead_set ||= - kernels.to_h {|item| - value = - if item.position > 1 - prev_state, prev_item = predecessor_with_item(item) - prev_state.item_lookahead_set[prev_item] - elsif item.position == 1 - prev_state = predecessors.find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } } - shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs } - prev_state.goto_follows(shift, next_state) - else - [] - end - [item, value] + if inadequacy.nil? && lhs_adequacy + next nil + else + predecessor.kernels.to_h {|pred_k| + [pred_k, kernels.any? {|k| + inadequacy[k] && ( + pred_k.predecessor_item_of?(k) && predecessor.item_lookahead_set[pred_k].include?(token) || + k.position == 1 && predecessor.lhs_contributions(k.lhs, token)[pred_k] + ) + }] + } + end } - end - - def item_lookahead_set=(k) - @item_lookahead_set = k - end - - def predecessor_with_item(item) - predecessors.each do |state| - state.kernels.each do |kernel| - return [state, kernel] if kernel.rule == item.rule && kernel.position == item.position - 1 - end - end + } end def lhs_contributions(sym, token) shift, next_state = nterm_transitions.find {|sh, _| sh.next_sym == sym } if always_follows(shift, next_state).include?(token) - [] + nil else - kernels.map {|kernel| [kernel, follow_kernel?(kernel) && item_lookahead_set[kernel].include?(token)] } + kernels.to_h {|kernel| [kernel, follow_kernel_items(shift, next_state, kernel) && item_lookahead_set[kernel].include?(token)] } end end - def follow_kernel?(item) - item.symbols_after_dot.all?(&:nullable) + def follow_kernel_items(shift, next_state, kernel) + queue = [[self, shift, next_state]] + until queue.empty? + st, sh, next_st = queue.pop + return true if kernel.next_sym == sh.next_sym && kernel.symbols_after_transition.all?(&:nullable) + st.internal_dependencies(sh, next_st).each {|v| queue << v } + end + false end - def follow_kernel_items(shift, next_state, item) - internal_dependencies[[shift, next_state]].any? {|shift, _| shift.next_sym == item.next_sym } && - item.symbols_after_dot.all?(&:nullable) + def item_lookahead_set + kernels.to_h {|item| + value = + if item.lhs.accept_symbol? + [] + elsif item.position > 1 + prev_items = predecessors_with_item(item) + prev_items.map {|st, i| st.item_lookahead_set[i] }.reduce([]) {|acc, syms| acc |= syms } + elsif item.position == 1 + prev_state = @predecessors.find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } } + shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs } + prev_state.goto_follows(shift, next_state) + end + # pp self, item.inspect, value.inspect + [item, value] + } + end + + def item_lookahead_set=(k) + @item_lookahead_set = k + end + + def predecessors_with_item(item) + result = [] + @predecessors.each do |pre| + pre.items.each do |i| + result << [pre, i] if i.predecessor_item_of?(item) + end + end + result end def next_terms @@ -288,71 +352,85 @@ def append_predecessor(prev_state) @predecessors.uniq! end + def goto_follow_set(nterm_token) + return [] if nterm_token.accept_symbol? + shift, next_state = @lalr_isocore.nterm_transitions.find {|sh, _| sh.next_sym == nterm_token } + + @kernels + .select {|kernel| follow_kernel_items(shift, next_state, kernel) } + .map {|kernel| item_lookahead_set[kernel] } + .reduce(always_follows(shift, next_state)) {|result, terms| result |= terms } + end + def goto_follows(shift, next_state) - include_dependencies(shift, next_state).reduce([]) {|result, goto| - st, sh, next_st = goto - result.union(st.always_follows(sh, next_st)) - } + queue = internal_dependencies(shift, next_state) + predecessor_dependencies(shift, next_state) + terms = always_follows(shift, next_state) + until queue.empty? + st, sh, next_st = queue.pop + terms |= st.always_follows(sh, next_st) + st.internal_dependencies(sh, next_st).each {|v| queue << v } + st.predecessor_dependencies(sh, next_st).each {|v| queue << v } + end + terms + end + + def always_follows(shift, next_state) + queue = internal_dependencies(shift, next_state) + successor_dependencies(shift, next_state) + terms = term_transitions.filter_map {|sh, _| sh.next_sym if sh == shift } + until queue.empty? + st, sh, next_st = queue.pop + terms |= st.term_transitions.map {|sh, _| sh.next_sym } + st.internal_dependencies(sh, next_st).each {|v| queue << v } + st.successor_dependencies(sh, next_st).each {|v| queue << v } + end + terms end - def include_dependencies(shift, next_state) - internal = internal_dependencies[[shift, next_state]] - pred = predecessor_dependencies(shift, next_state) + def internal_dependencies(shift, next_state) + return @internal_dependencies[[shift, next_state]] if @internal_dependencies[[shift, next_state]] - return internal if pred.empty? - dependency = internal.union(pred) + syms = @items.select {|i| + i.next_sym == shift.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0 + }.map(&:lhs).uniq + dependencies = nterm_transitions.select {|sh, _| syms.include?(sh.next_sym) }.map {|goto| [self, *goto] } - dependency.reduce(dependency) {|result, goto| result.union(compute_include_dependencies(*goto)) } + @internal_dependencies[[shift, next_state]] = dependencies end - def predecessor_dependencies - @predecessor_dependencies ||= - @kernels.to_h {|kernel| + def successor_dependencies(shift, next_state) + return @successor_dependencies[[shift, next_state]] if @successor_dependencies[[shift, next_state]] - } - item = kernels.find {|kernel| kernel.next_sym == shift.next_sym } - return [] unless item.symbols_after_transition.all?(&:nullable) - - st = @predecessors.find {|p| p.items.find {|i| i.rule == item.rule && i.position == item.position - 1 } } - sh, next_st = s.nterm_transitions.find {|shift, _| shift.next_token == item.lhs } - [[s, sh, next_st]] - end - - def internal_dependencies - @internal_dependencies ||= - nterm_transitions.to_h {|shift, next_state| - items = @items.select {|i| i.next_sym == shift.next_sym } - syms = [] - until items.empty? - item = items.pop - if item.symbols_after_transition.all?(&:nullable) && @items.any? {|i| i.next_sym == item.lhs } - syms << item.lhs - @items.each {|i| items << i if i.next_sym == item.lhs } - end - end + dependencies = next_state.nterm_transitions + .select {|next_shift, _| next_shift.next_sym.nullable } + .map {|transition| [next_state, *transition] } - transitions = syms.map {|sym| - nterm_transitions.find {|other_shift, _| other_shift.next_sym == sym }.unshift(self) - } - [[shift, next_state], transitions] - } + @successor_dependencies[[shift, next_state]] = dependencies end - def successor_dependencies - @successor_dependencies ||= - nterm_transitions.to_h {|shift, next_state| - transitions = [] - queue = [] - next_state.nterm_transitions.each {|next_shift, next_next_state| queue << [next_state, next_shift, next_next_state] } - until queue.empty? - next_st, next_sh, next_next_st = queue.pop - if next_sh.next_sym.nullable - transitions << [next_st, next_sh, next_next_st] - next_next_st.nterm_transitions.each {|sh, st| queue << [next_next_st, sh, st] } - end + def predecessor_dependencies(shift, next_state) + return @predecessor_dependencies[[shift, next_state]] if @predecessor_dependencies[[shift, next_state]] + + state_items = [] + @kernels.filter {|kernel| + kernel.next_sym == shift.next_sym && kernel.symbols_after_transition.all?(&:nullable) + }.each do |item| + queue = predecessors_with_item(item) + until queue.empty? + st, i = queue.pop + if i.position == 0 + state_items << [st, i] + else + st.predecessors_with_item(i).each {|v| queue << v } end - [[shift, next_state], transitions] - } + end + end + + dependencies = state_items.map {|state, item| + sh, next_st = state.nterm_transitions.find {|shi, _| shi.next_sym == item.lhs } + [state, sh, next_st] + } + + @predecessor_dependencies[[shift, next_state]] = dependencies end end end diff --git a/lib/lrama/state/reduce.rb b/lib/lrama/state/reduce.rb index a2b7c26c..65e6fa6c 100644 --- a/lib/lrama/state/reduce.rb +++ b/lib/lrama/state/reduce.rb @@ -17,6 +17,10 @@ def rule @item.rule end + def inspect + "(Reduce by rule #{item.inspect}, [#{(@look_ahead || []).map(&:inspect).join(', ')}])" + end + def look_ahead=(look_ahead) @look_ahead = look_ahead.freeze end diff --git a/lib/lrama/state/shift.rb b/lib/lrama/state/shift.rb index 81ef013a..7c22f688 100644 --- a/lib/lrama/state/shift.rb +++ b/lib/lrama/state/shift.rb @@ -10,6 +10,10 @@ def initialize(next_sym, next_items) @next_sym = next_sym @next_items = next_items end + + def inspect + "(Shift, #{@next_sym.inspect})" + end end end end diff --git a/lib/lrama/states.rb b/lib/lrama/states.rb index 4f32d9b0..c991a4b6 100644 --- a/lib/lrama/states.rb +++ b/lib/lrama/states.rb @@ -95,7 +95,7 @@ def compute def compute_ielr report_duration(:compute_predecessors) { compute_predecessors } report_duration(:split_states) { split_states } - @states.each {|state| p state, state.transitions, state.item_lookahead_set } + @states.each {|state| pp state, state.item_lookahead_set.inspect, state.transitions.map {|shift, next_state| [shift, next_state, shift.next_sym.nterm? && state.goto_follow_set(shift.next_sym), shift.next_sym.nterm? && state.goto_follows(shift, next_state)] } } report_duration(:compute_direct_read_sets) { compute_direct_read_sets } report_duration(:compute_reads_relation) { compute_reads_relation } report_duration(:compute_read_sets) { compute_read_sets } @@ -248,7 +248,7 @@ def enqueue_state(states, state) # Trace previous = state.kernels.first.previous_sym trace_state do |out| - out << sprintf("state_list_append (state = %d, symbol = %d (%s))", + out << sprintf("state_list_append (state = %d, symbol = %d (%s))\n", @states.count, previous.number, previous.display_name) end @@ -550,14 +550,13 @@ def compute_predecessors end def split_states - @ielr_isocores = Hash.new {|hash, key| hash[key] = [key] } - @lookaheads_recomputed = Hash.new {|hash, key| hash[key] = false } transition_queue = [] @states.first.transitions.each do |shift, next_state| transition_queue << [@states.first, shift, next_state] end until transition_queue.empty? state, shift, next_state = transition_queue.shift + # pp state, shift, next_state compute_state(state, shift, next_state) next_state.transitions.each do |sh, next_st| transition_queue << [next_state, sh, next_st] @@ -565,21 +564,22 @@ def split_states end end - def merge_lookaheads(state, k) - return if state.kernels.all? {|item| (k[item] - state.item_lookahead_set[item]).empty? } + def merge_lookaheads(state, filtered_lookaheads) + return if state.kernels.all? {|item| (filtered_lookaheads[item] - state.item_lookahead_set[item]).empty? } state.transitions.each do |shift, next_state| - next if @lookaheads_recomputed[next_state] + next if next_state.lookaheads_recomputed compute_state(state, shift, next_state) end end def compute_state(state, shift, next_state) - k = propagate_lookaheads(state, next_state) - s = @ielr_isocores[next_state].find {|st| is_compatible(st, k) } + filtered_lookaheads = state.propagate_lookaheads(next_state) + # pp filtered_lookaheads.transform_keys(&:to_s) + s = next_state.ielr_isocores.find {|st| st.compatible_lookahead?(filtered_lookaheads) } if s.nil? - s = @ielr_isocores[next_state].last + s = next_state.ielr_isocores.last new_state = State.new(@states.count, s.accessing_symbol, s.kernels) new_state.closure = s.closure new_state.compute_shifts_reduces @@ -592,75 +592,14 @@ def compute_state(state, shift, next_state) @ielr_isocores[s].each do |st| @ielr_isocores[st] = @ielr_isocores[s] end - @lookaheads_recomputed[new_state] = true - new_state.item_lookahead_set = k + new_state.item_lookahead_set = filtered_lookaheads state.update_transition(shift, new_state) - elsif(!@lookaheads_recomputed[s]) - s.item_lookahead_set = k - @lookaheads_recomputed[s] = true + elsif(!s.lookaheads_recomputed) + s.item_lookahead_set = filtered_lookaheads else state.update_transition(shift, s) - merge_lookaheads(s, k) + merge_lookaheads(s, filtered_lookaheads) end end - - def propagate_lookaheads(state, next_state) - next_state.kernels.to_h {|item| - lookahead_sets = - if item.position == 1 - compute_goto_follow_set(state.lalr_isocore, item.lhs) - else - kernel = state.kernels.find {|k| k.rule == item.rule && k.position == item.position - 1 } - state.item_lookahead_set[kernel] - end - - # p [state, lookahead_sets, lookahead_set_filters(next_state)[item]] - - [item, lookahead_sets & lookahead_set_filters(next_state)[item]] - } - end - - def lookahead_set_filters(state) - p state - state.kernels.to_h {|kernel| - # p [state, kernel, annotation_list(@lalr_isocores[state])] - [kernel, - state.lalr_isocore.annotation_list.filter_map {|token, actions| - token if token.term? && actions.any? {|item, _| item == kernel } - }] - } - end - - def is_compatible(state, k) - !@lookaheads_recomputed[state] || - annotation_list(state.lalr_isocores).all? {|token, actions| - a = dominant_contribution(state, token, actions, state.item_lookahead_set) - b = dominant_contribution(state, token, actions, k) - a.empty? || b.empty? || a == b - } - end - - def dominant_contribution(state, token, actions, lookaheads) - actions.filter_map {|action, items| - action if items.empty? || items.any? {|item, bool| bool && lookaheads[item].include?(token) } - }.reject {|action| - if action.is_a?(State::Shift) - action.not_selected - elsif action.is_a?(State::Reduce) - action.not_selected_symbols.include?(token) - end - } - end - - def compute_goto_follow_set(state, nterm_token) - shift, next_state = state.nterm_transitions.find {|shift, _| shift.next_sym == nterm_token } - return [] if shift.nil? && nterm_token.id.s_value == '$accept' - - state.always_follows(shift, next_state).union(state.kernels.select {|item| - state.follow_kernel_items(shift, next_state, item) - }.reduce([]) {|result, item| - result.union(state.item_lookahead_set[item]) - }) - end end end diff --git a/lib/lrama/states/item.rb b/lib/lrama/states/item.rb index 08a8df91..0c5405f9 100644 --- a/lib/lrama/states/item.rb +++ b/lib/lrama/states/item.rb @@ -72,6 +72,10 @@ def to_s "#{lhs.id.s_value}: #{display_name}" end + def inspect + to_s + end + def display_name r = rhs.map(&:display_name).insert(position, "•").join(" ") "#{r} (rule #{rule_id})" @@ -82,6 +86,10 @@ def display_rest r = symbols_after_dot.map(&:display_name).join(" ") ". #{r} (rule #{rule_id})" end + + def predecessor_item_of?(other_item) + rule == other_item.rule && position == other_item.position - 1 + end end end end