summaryrefslogtreecommitdiff
path: root/tool/lrama/lib/lrama/states_reporter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'tool/lrama/lib/lrama/states_reporter.rb')
-rw-r--r--tool/lrama/lib/lrama/states_reporter.rb310
1 files changed, 310 insertions, 0 deletions
diff --git a/tool/lrama/lib/lrama/states_reporter.rb b/tool/lrama/lib/lrama/states_reporter.rb
new file mode 100644
index 0000000000..25893e61be
--- /dev/null
+++ b/tool/lrama/lib/lrama/states_reporter.rb
@@ -0,0 +1,310 @@
+module Lrama
+ class StatesReporter
+ include Lrama::Report::Duration
+
+ def initialize(states)
+ @states = states
+ end
+
+ def report(io, **options)
+ report_duration(:report) do
+ _report(io, **options)
+ end
+ end
+
+ private
+
+ def _report(io, grammar: false, states: false, itemsets: false, lookaheads: false, solved: false, verbose: false)
+ # TODO: Unused terms
+ # TODO: Unused rules
+
+ report_conflicts(io)
+ report_grammar(io) if grammar
+ report_states(io, itemsets, lookaheads, solved, verbose)
+ end
+
+ def report_conflicts(io)
+ has_conflict = false
+
+ @states.states.each do |state|
+ messages = []
+ cs = state.conflicts.group_by(&:type)
+ if cs[:shift_reduce]
+ messages << "#{cs[:shift_reduce].count} shift/reduce"
+ end
+
+ if cs[:reduce_reduce]
+ messages << "#{cs[:reduce_reduce].count} reduce/reduce"
+ end
+
+ if !messages.empty?
+ has_conflict = true
+ io << "State #{state.id} conflicts: #{messages.join(', ')}\n"
+ end
+ end
+
+ if has_conflict
+ io << "\n\n"
+ end
+ end
+
+ def report_grammar(io)
+ io << "Grammar\n"
+ last_lhs = nil
+
+ @states.rules.each do |rule|
+ if rule.rhs.empty?
+ r = "ε"
+ else
+ r = rule.rhs.map(&:display_name).join(" ")
+ end
+
+ if rule.lhs == last_lhs
+ io << sprintf("%5d %s| %s\n", rule.id, " " * rule.lhs.display_name.length, r)
+ else
+ io << "\n"
+ io << sprintf("%5d %s: %s\n", rule.id, rule.lhs.display_name, r)
+ end
+
+ last_lhs = rule.lhs
+ end
+ io << "\n\n"
+ end
+
+ def report_states(io, itemsets, lookaheads, solved, verbose)
+ @states.states.each do |state|
+ # Report State
+ io << "State #{state.id}\n\n"
+
+ # Report item
+ last_lhs = nil
+ list = itemsets ? state.items : state.kernels
+ list.sort_by {|i| [i.rule_id, i.position] }.each do |item|
+ rule = item.rule
+ position = item.position
+ if rule.rhs.empty?
+ r = "ε •"
+ else
+ r = rule.rhs.map(&:display_name).insert(position, "•").join(" ")
+ end
+ if rule.lhs == last_lhs
+ l = " " * rule.lhs.id.s_value.length + "|"
+ else
+ l = rule.lhs.id.s_value + ":"
+ end
+ la = ""
+ if lookaheads && item.end_of_rule?
+ reduce = state.find_reduce_by_item!(item)
+ look_ahead = reduce.selected_look_ahead
+ if !look_ahead.empty?
+ la = " [#{look_ahead.map(&:display_name).join(", ")}]"
+ end
+ end
+ last_lhs = rule.lhs
+
+ io << sprintf("%5i %s %s%s\n", rule.id, l, r, la)
+ end
+ io << "\n"
+
+
+ # Report shifts
+ tmp = state.term_transitions.select do |shift, _|
+ !shift.not_selected
+ end.map do |shift, next_state|
+ [shift.next_sym, next_state.id]
+ end
+ max_len = tmp.map(&:first).map(&:display_name).map(&:length).max
+ tmp.each do |term, state_id|
+ io << " #{term.display_name.ljust(max_len)} shift, and go to state #{state_id}\n"
+ end
+ io << "\n" if !tmp.empty?
+
+
+ # Report error caused by %nonassoc
+ nl = false
+ tmp = state.resolved_conflicts.select do |resolved|
+ resolved.which == :error
+ end.map do |error|
+ error.symbol.display_name
+ end
+ max_len = tmp.map(&:length).max
+ tmp.each do |name|
+ nl = true
+ io << " #{name.ljust(max_len)} error (nonassociative)\n"
+ end
+ io << "\n" if !tmp.empty?
+
+
+ # Report reduces
+ nl = false
+ max_len = state.non_default_reduces.flat_map(&:look_ahead).compact.map(&:display_name).map(&:length).max || 0
+ max_len = [max_len, "$default".length].max if state.default_reduction_rule
+ ary = []
+
+ state.non_default_reduces.each do |reduce|
+ reduce.look_ahead.each do |term|
+ ary << [term, reduce]
+ end
+ end
+
+ ary.sort_by do |term, reduce|
+ term.number
+ end.each do |term, reduce|
+ rule = reduce.item.rule
+ io << " #{term.display_name.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.display_name})\n"
+ nl = true
+ end
+
+ if r = state.default_reduction_rule
+ nl = true
+ s = "$default".ljust(max_len)
+
+ if r.initial_rule?
+ io << " #{s} accept\n"
+ else
+ io << " #{s} reduce using rule #{r.id} (#{r.lhs.display_name})\n"
+ end
+ end
+ io << "\n" if nl
+
+
+ # Report nonterminal transitions
+ tmp = []
+ max_len = 0
+ state.nterm_transitions.each do |shift, next_state|
+ nterm = shift.next_sym
+ tmp << [nterm, next_state.id]
+ max_len = [max_len, nterm.id.s_value.length].max
+ end
+ tmp.uniq!
+ tmp.sort_by! do |nterm, state_id|
+ nterm.number
+ end
+ tmp.each do |nterm, state_id|
+ io << " #{nterm.id.s_value.ljust(max_len)} go to state #{state_id}\n"
+ end
+ io << "\n" if !tmp.empty?
+
+
+ if solved
+ # Report conflict resolutions
+ state.resolved_conflicts.each do |resolved|
+ io << " #{resolved.report_message}\n"
+ end
+ io << "\n" if !state.resolved_conflicts.empty?
+ end
+
+
+ if verbose
+ # Report direct_read_sets
+ io << " [Direct Read sets]\n"
+ direct_read_sets = @states.direct_read_sets
+ @states.nterms.each do |nterm|
+ terms = direct_read_sets[[state.id, nterm.token_id]]
+ next if !terms
+ next if terms.empty?
+
+ str = terms.map {|sym| sym.id.s_value }.join(", ")
+ io << " read #{nterm.id.s_value} shift #{str}\n"
+ end
+ io << "\n"
+
+
+ # Reprot reads_relation
+ io << " [Reads Relation]\n"
+ @states.nterms.each do |nterm|
+ a = @states.reads_relation[[state.id, nterm.token_id]]
+ next if !a
+
+ a.each do |state_id2, nterm_id2|
+ n = @states.nterms.find {|n| n.token_id == nterm_id2 }
+ io << " (State #{state_id2}, #{n.id.s_value})\n"
+ end
+ end
+ io << "\n"
+
+
+ # Reprot read_sets
+ io << " [Read sets]\n"
+ read_sets = @states.read_sets
+ @states.nterms.each do |nterm|
+ terms = read_sets[[state.id, nterm.token_id]]
+ next if !terms
+ next if terms.empty?
+
+ terms.each do |sym|
+ io << " #{sym.id.s_value}\n"
+ end
+ end
+ io << "\n"
+
+
+ # Reprot includes_relation
+ io << " [Includes Relation]\n"
+ @states.nterms.each do |nterm|
+ a = @states.includes_relation[[state.id, nterm.token_id]]
+ next if !a
+
+ a.each do |state_id2, nterm_id2|
+ n = @states.nterms.find {|n| n.token_id == nterm_id2 }
+ io << " (State #{state.id}, #{nterm.id.s_value}) -> (State #{state_id2}, #{n.id.s_value})\n"
+ end
+ end
+ io << "\n"
+
+
+ # Report lookback_relation
+ io << " [Lookback Relation]\n"
+ @states.rules.each do |rule|
+ a = @states.lookback_relation[[state.id, rule.id]]
+ next if !a
+
+ a.each do |state_id2, nterm_id2|
+ n = @states.nterms.find {|n| n.token_id == nterm_id2 }
+ io << " (Rule: #{rule.to_s}) -> (State #{state_id2}, #{n.id.s_value})\n"
+ end
+ end
+ io << "\n"
+
+
+ # Reprot follow_sets
+ io << " [Follow sets]\n"
+ follow_sets = @states.follow_sets
+ @states.nterms.each do |nterm|
+ terms = follow_sets[[state.id, nterm.token_id]]
+
+ next if !terms
+
+ terms.each do |sym|
+ io << " #{nterm.id.s_value} -> #{sym.id.s_value}\n"
+ end
+ end
+ io << "\n"
+
+
+ # Report LA
+ io << " [Look-Ahead Sets]\n"
+ tmp = []
+ max_len = 0
+ @states.rules.each do |rule|
+ syms = @states.la[[state.id, rule.id]]
+ next if !syms
+
+ tmp << [rule, syms]
+ max_len = ([max_len] + syms.map {|s| s.id.s_value.length }).max
+ end
+ tmp.each do |rule, syms|
+ syms.each do |sym|
+ io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n"
+ end
+ end
+ io << "\n" if !tmp.empty?
+ end
+
+
+ # End of Report State
+ io << "\n"
+ end
+ end
+ end
+end