Skip to content

Commit 2d975c5

Browse files
committed
Support IELR(1) parser generation
1 parent e4a27f0 commit 2d975c5

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed

lib/lrama/state.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ def transitions
8282
@transitions ||= shifts.map {|shift| [shift, @items_to_state[shift.next_items]] }
8383
end
8484

85+
def update_transition(shift, next_state)
86+
set_items_to_state(shift.next_items, next_state)
87+
@transitions = shifts.map {|sh| [sh, @items_to_state[sh.next_items]] }
88+
end
89+
8590
def selected_term_transitions
8691
term_transitions.reject do |shift, next_state|
8792
shift.not_selected
@@ -140,5 +145,45 @@ def rr_conflicts
140145
conflict.type == :reduce_reduce
141146
end
142147
end
148+
149+
def always_follows(shift, next_state)
150+
internal_dependencies(shift, next_state).union(successor_dependencies(shift, next_state)).reduce([]) {|result, transition| result += transition[1].term_transitions.map {|shift, _| shift.next_sym } }
151+
end
152+
153+
def internal_dependencies(shift, next_state)
154+
nterm_transitions.select {|other_shift, _|
155+
@items.find {|item| item.next_sym == shift.next_sym && item.lhs == other_shift.next_sym && item.symbols_after_dot.all?(&:nullable) }
156+
}.reduce([[shift, next_state]]) {|result, transition|
157+
result += internal_follows(*transition)
158+
}
159+
end
160+
161+
def successor_dependencies(shift, next_state)
162+
next_state.nterm_transitions.select {|other_shift, _|
163+
other_shift.next_sym.nullable
164+
}.reduce([[shift, next_state]]) {|result, transition|
165+
result += successor_dependencies(*transition)
166+
}
167+
end
168+
169+
def inadequacy_list
170+
return @inadequacy_list if @inadequacy_list
171+
172+
list = shifts.to_h {|shift| [shift.next_sym, [[shift, nil]]] }
173+
reduces.each do |reduce|
174+
reduce_list = (reduce.look_ahead || []).to_h {|sym| [sym, [[reduce, reduce.item]]] }
175+
list.merge!(reduce_list) {|_, list_value, reduce_value| list_value + reduce_value }
176+
end
177+
178+
@inadequacy_list = {self => list.select {|_, actions| actions.size > 1 }}
179+
end
180+
181+
def follow_kernel?(item)
182+
item.symbols_after_dot.all?(&:nullable)
183+
end
184+
185+
def follow_kernel_items(shift, next_state, item)
186+
internal_dependencies(shift, next_state).any? {|shift, _| shift.next_sym == item.next_sym } && item.symbols_after_dot.all?(&:nullable)
187+
end
143188
end
144189
end

lib/lrama/states.rb

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ def compute
9393
check_conflicts
9494
end
9595

96+
def compute_ielr
97+
report_duration(:split_states) { split_states }
98+
end
99+
96100
def reporter
97101
StatesReporter.new(self)
98102
end
@@ -552,5 +556,225 @@ def check_conflicts
552556
end
553557
end
554558
end
559+
560+
def split_states
561+
@item_lookahead_set = {}
562+
@lalr_isocores = Hash.new {|hash, key| hash[key] = key }
563+
@ielr_isocores = Hash.new {|hash, key| hash[key] = [key] }
564+
@lookaheads_recomputed = Hash.new {|hash, key| hash[key] = false }
565+
@predecessors = {}
566+
@states.each do |state|
567+
state.transitions.each do |shift, next_state|
568+
compute_state(state, shift, next_state)
569+
end
570+
end
571+
end
572+
573+
def merge_lookaheads(state, k)
574+
return if state.kernels.all? {|item| (k[item] - item_lookahead_set(state)[item]).empty? }
575+
576+
state.transitions.each do |shift, next_state|
577+
next if @lookaheads_recomputed[next_state]
578+
compute_state(state, shift, next_state)
579+
end
580+
end
581+
582+
def compute_state(state, shift, next_state)
583+
k = propagate_lookaheads(state, next_state)
584+
s = @ielr_isocores[next_state].find {|st| is_compatible(st, k) }
585+
586+
if s.nil?
587+
s = @ielr_isocores[next_state].last
588+
new_state = State.new(@states.count, s.accessing_symbol, s.kernels)
589+
new_state.closure = s.closure
590+
new_state.compute_shifts_reduces
591+
s.transitions.each do |sh, next_state|
592+
new_state.set_items_to_state(sh.next_items, next_state)
593+
end
594+
@states << new_state
595+
@lalr_isocores[new_state] = s
596+
@ielr_isocores[s] << new_state
597+
@ielr_isocores[s].each do |st|
598+
@ielr_isocores[st] = @ielr_isocores[s]
599+
end
600+
@lookaheads_recomputed[new_state] = true
601+
@item_lookahead_set[new_state] = k
602+
state.update_transition(shift, new_state)
603+
elsif(!@lookaheads_recomputed[s])
604+
@item_lookahead_set[s] = k
605+
@lookaheads_recomputed[s] = true
606+
else
607+
merge_lookaheads(s, k)
608+
end
609+
end
610+
611+
def propagate_lookaheads(state, next_state)
612+
next_state.kernels.to_h {|item|
613+
lookahead_sets =
614+
if item.position == 1
615+
compute_goto_follow_set(@lalr_isocores[state], item.lhs)
616+
else
617+
kernel = state.kernels.find {|k| k.rule == item.rule && k.position == item.position - 1 }
618+
item_lookahead_set(state)[kernel]
619+
end
620+
621+
[item, lookahead_sets & lookahead_set_filters(next_state)[item]]
622+
}
623+
end
624+
625+
def lookahead_set_filters(state)
626+
state.kernels.to_h {|kernel|
627+
[kernel,
628+
annotation_list(@lalr_isocores[state])[@lalr_isocores[state]].filter_map {|token, actions|
629+
token if token.term? && actions.any? {|item, _| item == kernel }
630+
}]
631+
}
632+
end
633+
634+
def is_compatible(state, k)
635+
@lookaheads_recomputed[state] ||
636+
annotation_list(@lalr_isocores[state])[@lalr_isocores[state]].all? {|token, actions|
637+
a = dominant_contribution(state, token, actions, item_lookahead_set(state))
638+
b = dominant_contribution(state, token, actions, k)
639+
a.empty? || b.empty? || a == b
640+
}
641+
end
642+
643+
def dominant_contribution(state, token, actions, lookaheads)
644+
actions.filter_map {|action, items|
645+
action if items.empty? || items.any? {|item, bool| bool && lookaheads[item].include?(token) }
646+
}.reject {|action|
647+
if action.is_a?(State::Shift)
648+
action.not_selected
649+
elsif action.is_a?(State::Reduce)
650+
action.not_selected_symbols.include?(token)
651+
end
652+
}
653+
end
654+
655+
def compute_goto_follow_set(state, nterm_token)
656+
shift, next_state = state.nterm_transitions.find {|shift, _| shift.next_sym == nterm_token }
657+
return [] if shift.nil? && nterm_token.id.s_value == '$accept'
658+
state.always_follows(shift, next_state).union(state.kernels.select {|item|
659+
state.follow_kernel_items(shift, next_state, item)
660+
}.reduce([]) {|result, item|
661+
result.union(item_lookahead_set(state)[item])
662+
})
663+
end
664+
665+
def item_lookahead_set(state)
666+
@item_lookahead_set[state] ||=
667+
state.kernels.to_h {|item|
668+
value =
669+
if item.position > 1
670+
prev_state, prev_item = predecessor_with_item(state, item)
671+
item_lookahead_set(prev_state)[prev_item]
672+
elsif item.position == 1
673+
prev_state = predecessors(state).find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } }
674+
shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs }
675+
goto_follows(prev_state, shift, next_state)
676+
else
677+
[]
678+
end
679+
[item, value]
680+
}
681+
end
682+
683+
def predecessors(state)
684+
@predecessors[state] ||= @states.select {|prev| prev.transitions.any? {|_, to_state| to_state == state } }
685+
end
686+
687+
def predecessor_with_item(state, item)
688+
predecessors(state).each do |state|
689+
state.kernels.each do |kernel|
690+
return [state, kernel] if kernel.rule == item.rule && kernel.position == item.position - 1
691+
end
692+
end
693+
end
694+
695+
def goto_follows(state, shift, next_state)
696+
compute_include_dependencies(state, shift, next_state).reduce([]) {|result, goto|
697+
st, sh, next_st = goto
698+
result.union(st.always_follows(sh, next_st))
699+
}
700+
end
701+
702+
def compute_include_dependencies(state, shift, next_state)
703+
internal = state.internal_dependencies(shift, next_state).map {|sh, next_st| [state, sh, next_st] }
704+
705+
item = state.kernels.find {|kernel| kernel.next_sym == shift.next_sym }
706+
return internal unless item.symbols_after_dot.all?(&:nullable)
707+
708+
s, i = state, item
709+
while i.position > 0
710+
s = predecessors(s).find {|p| p.kernels.find {|item| item.rule == i.rule && item.position == i.position - 1 } }
711+
i = s.kernels.find {|item| item.rule == i.rule && item.position == i.position - 1 }
712+
end
713+
714+
p_shift, p_next_state = s.transitions.find {|sh, _| sh.next_sym == item.lhs }
715+
internal.union([[s, p_shift, p_next_state]])
716+
end
717+
718+
def annotation_list(state)
719+
manifestations = state.inadequacy_list.transform_values {|hash| hash.to_h {|token, actions| [token, annotate_manifestation(state, token, actions)] } }
720+
state.transitions.reduce(manifestations) {|item, transition|
721+
item.merge(annotate_predecessor(state, transition[1], annotation_list(transition[1])[transition[1]])) {|state, annotations, other_annotations|
722+
annotations.merge(other_annotations) {|token, actions, other_actions|
723+
actions.merge(other_actions) {|action, items, other_items|
724+
items.merge(other_items) {|item, bool, other_bool|
725+
raise if bool != other_bool
726+
bool
727+
}
728+
}
729+
}
730+
}
731+
}
732+
end
733+
734+
def annotate_manifestation(state, token, actions)
735+
actions.to_h {|action, item|
736+
[action,
737+
if action.is_a?(State::Shift)
738+
{}
739+
elsif action.is_a?(State::Reduce)
740+
if item.empty_rule?
741+
compute_lhs_contributions(state, item.lhs, token)
742+
else
743+
state.kernels.to_h {|kernel| [kernel, kernel.rule == item.rule && kernel.end_of_rule?] }
744+
end
745+
end
746+
]
747+
}
748+
end
749+
750+
def compute_lhs_contributions(state, sym, token)
751+
shift, next_state = state.nterm_transitions.find {|sh, _| sh.next_sym == sym }
752+
if state.always_follows(shift, next_state).include?(token)
753+
{}
754+
else
755+
state.kernels.to_h {|kernel| [kernel, state.follow_kernel?(kernel) && item_lookahead_set(state)[kernel].include?(token)] }
756+
end
757+
end
758+
759+
def annotate_predecessor(state, next_state, annotation_list)
760+
{state => annotation_list.to_h {|token, actions|
761+
next [token, {}] if actions.empty? || actions.any? {|action, hash|
762+
hash.keys.any? {|item| hash[item] && item.position == 1 && compute_lhs_contributions(state, item.lhs, token).empty? }
763+
}
764+
[token, actions.to_h {|action, hash|
765+
[action, hash.to_h {|item, _|
766+
kernel = state.kernels.find {|k| k.rule == item.rule && k.position == item.position - 1 }
767+
[kernel,
768+
hash[item] &&
769+
(
770+
!kernel.nil? && (item_lookahead_set(state)[kernel].include?(token)) ||
771+
(item.position == 1 && compute_lhs_contributions(state, item.lhs, token)[item])
772+
)
773+
]
774+
}]
775+
}]
776+
}
777+
}
778+
end
555779
end
556780
end

0 commit comments

Comments
 (0)