Skip to content

Commit

Permalink
Support IELR(1) parser generation
Browse files Browse the repository at this point in the history
  • Loading branch information
junk0612 committed Oct 11, 2024
1 parent c62b8e3 commit 9f49330
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 9 deletions.
1 change: 1 addition & 0 deletions lib/lrama/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def run(argv)
end
states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states.compute
states.compute_ielr if grammar.ielr_defined?
context = Lrama::Context.new(states)

if options.report_file
Expand Down
4 changes: 4 additions & 0 deletions lib/lrama/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ def find_rules_by_symbol(sym)
@sym_to_rules[sym.number]
end

def ielr_defined?
@define.key?('lr.type') && @define['lr.type'] == 'ielr'
end

private

def compute_nullable
Expand Down
2 changes: 1 addition & 1 deletion lib/lrama/option_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def parse_by_option_parser(argv)
o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v }
o.on('-t', 'reserved, do nothing') { }
o.on('--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
o.on('-D', '--define=NAME[=VALUE]', "similar to '%define NAME VALUE'") {|v| @options.define = v }
o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") {|v| @options.define = v }
o.separator ''
o.separator 'Output:'
o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
Expand Down
6 changes: 0 additions & 6 deletions lib/lrama/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,5 @@ def initialize
@y = STDIN
@debug = false
end

def define=(v)
v.split(',').each do |p_define|
@define.store *p_define.split('=')
end
end
end
end
273 changes: 272 additions & 1 deletion lib/lrama/state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Lrama
class State
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
:default_reduction_rule, :closure, :items
attr_accessor :shifts, :reduces
attr_accessor :shifts, :reduces, :ielr_isocores, :lalr_isocore

def initialize(id, accessing_symbol, kernels)
@id = id
Expand All @@ -23,6 +23,9 @@ def initialize(id, accessing_symbol, kernels)
@conflicts = []
@resolved_conflicts = []
@default_reduction_rule = nil
@predecessors = []
@lalr_isocore = self
@ielr_isocores = [self]
end

def closure=(closure)
Expand Down Expand Up @@ -84,6 +87,18 @@ def transitions
@transitions ||= shifts.map {|shift| [shift, @items_to_state[shift.next_items]] }
end

def update_transition(shift, next_state)
set_items_to_state(shift.next_items, next_state)
next_state.append_predecessor(self)
clear_transitions_cache
end

def clear_transitions_cache
@nterm_transitions = nil
@term_transitions = nil
@transitions = nil
end

def selected_term_transitions
term_transitions.reject do |shift, next_state|
shift.not_selected
Expand Down Expand Up @@ -142,5 +157,261 @@ def rr_conflicts
conflict.type == :reduce_reduce
end
end

def propagate_lookaheads(next_state)
next_state.kernels.map {|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

[item, lookahead_sets & next_state.lookahead_set_filters[item]]
}.to_h
end

def lookaheads_recomputed
!@item_lookahead_set.nil?
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.map {|kernel|
[kernel,
@lalr_isocore.annotation_list.select {|token, actions|
token.term? && actions.any? {|action, contributions|
!contributions.nil? && contributions.key?(kernel) && contributions[kernel]
}
}.map {|token, _| token }
]
}.to_h
end

def dominant_contribution(token, actions, lookaheads)
a = actions.select {|action, contributions|
contributions.nil? || contributions.any? {|item, contributed| contributed && lookaheads[item].include?(token) }
}.map {|action, _| action }
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

shift_contributions = shifts.map {|shift|
[shift.next_sym, [shift]]
}.to_h
reduce_contributions = reduces.map {|reduce|
(reduce.look_ahead || []).map {|sym|
[sym, [reduce]]
}.to_h
}.reduce(Hash.new([])) {|hash, cont|
hash.merge(cont) {|_, a, b| a | b }
}

list = shift_contributions.merge(reduce_contributions) {|_, a, b| a | b }
@inadequacy_list = list.select {|token, actions| token.term? && actions.size > 1 }
end

def annotation_list
manifestations = annotate_manifestation
predecessors = transitions.map {|_, next_state| next_state.annotate_predecessor(self) }
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
inadequacy_list.transform_values {|actions|
actions.map {|action|
if action.is_a?(Shift)
[action, nil]
elsif action.is_a?(Reduce)
if action.rule.empty_rule?
[action, lhs_contributions(action.rule.lhs, inadequacy_list.key(actions))]
else
contributions = kernels.map {|kernel| [kernel, kernel.rule == action.rule && kernel.end_of_rule?] }.to_h
[action, contributions]
end
end
}.to_h
}
end

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?
}
if lhs_adequacy
next nil
else
predecessor.kernels.map {|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]
)
}]
}.to_h
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_items(shift, next_state, kernel) && item_lookahead_set[kernel].include?(token)] }.to_h
end
end

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 item_lookahead_set
return @item_lookahead_set if @item_lookahead_set

kernels.map {|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
[item, value]
}.to_h
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 append_predecessor(prev_state)
@predecessors << 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)
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 = []
until queue.empty?
st, sh, next_st = queue.pop
terms |= next_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 internal_dependencies(shift, next_state)
syms = @items.select {|i|
i.next_sym == shift.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0
}.map(&:lhs).uniq
nterm_transitions.select {|sh, _| syms.include?(sh.next_sym) }.map {|goto| [self, *goto] }
end

def successor_dependencies(shift, next_state)
next_state.nterm_transitions
.select {|next_shift, _| next_shift.next_sym.nullable }
.map {|transition| [next_state, *transition] }
end

def predecessor_dependencies(shift, next_state)
state_items = []
@kernels.select {|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
end
end

state_items.map {|state, item|
sh, next_st = state.nterm_transitions.find {|shi, _| shi.next_sym == item.lhs }
[state, sh, next_st]
}
end
end
end
Loading

0 comments on commit 9f49330

Please sign in to comment.