From 76323f350e520e149446cbcd5d20c495db74e12e Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune Date: Thu, 13 Aug 2020 21:50:16 -0400 Subject: [PATCH] [Fixes #137] Improve reporting --- bin/racc | 37 +++++++++++++++++++++++++++++-------- lib/racc/grammar.rb | 34 ++++++++++++++++++---------------- test/assets/ifelse.y | 14 ++++++++++++++ test/test_racc_command.rb | 15 +++++++++++++++ 4 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 test/assets/ifelse.y diff --git a/bin/racc b/bin/racc index 89e66593..4b0cbfe5 100755 --- a/bin/racc +++ b/bin/racc @@ -184,8 +184,12 @@ def main log_useless states.grammar log_conflict states else - report_useless states.grammar - report_conflict states + has_useless = report_useless states.grammar + has_conflicts = report_conflict states + if has_useless || has_conflicts + preamble = make_logfile ? 'C' : 'Turn on logging with "-v" and c' + $stderr.puts %Q{#{preamble}heck ".output" file for details} + end end profiler.report @@ -201,13 +205,29 @@ def make_filename(path, suffix) path.sub(/(?:\..*?)?\z/, suffix) end +LIST_LIMIT = 10 +def report_list(enum, label) + c = enum.count + if c > 0 + $stderr.puts "#{c} #{label}:" + enum.first(LIST_LIMIT).each do |item| + $stderr.puts " #{yield item}" + end + $stderr.puts " ..." if c > LIST_LIMIT + end +end + +# @return [Boolean] if anything was reported def report_conflict(states) if states.should_report_srconflict? + reported = true $stderr.puts "#{states.n_srconflicts} shift/reduce conflicts" end if states.rrconflict_exist? + reported = true $stderr.puts "#{states.n_rrconflicts} reduce/reduce conflicts" end + reported end def log_conflict(states) @@ -222,16 +242,17 @@ def log_conflict(states) } end +# @return [Boolean] if anything was reported def report_useless(grammar) - if grammar.useless_nonterminal_exist? - $stderr.puts "#{grammar.n_useless_nonterminals} useless nonterminals" - end - if grammar.useless_rule_exist? - $stderr.puts "#{grammar.n_useless_rules} useless rules" - end + reported = report_list(grammar.each_useless_nonterminal, 'useless nonterminals', &:to_s) + + reported ||= report_list(grammar.each_useless_rule, 'useless rules') { |r| "##{r.ident} (#{r.target})" } + if grammar.start.useless? $stderr.puts 'fatal: start symbol does not derive any sentence' + reported = true end + reported end def log_useless(grammar) diff --git a/lib/racc/grammar.rb b/lib/racc/grammar.rb index fa815343..3444dfcc 100644 --- a/lib/racc/grammar.rb +++ b/lib/racc/grammar.rb @@ -86,14 +86,15 @@ def useless_nonterminal_exist? end def n_useless_nonterminals - @n_useless_nonterminals ||= - begin - n = 0 - @symboltable.each_nonterminal do |sym| - n += 1 if sym.useless? - end - n - end + @n_useless_nonterminals ||= each_useless_nonterminal.count + end + + def each_useless_nonterminal + return to_enum __method__ unless block_given? + + @symboltable.each_nonterminal do |sym| + yield sym if sym.useless? + end end def useless_rule_exist? @@ -101,14 +102,15 @@ def useless_rule_exist? end def n_useless_rules - @n_useless_rules ||= - begin - n = 0 - each do |r| - n += 1 if r.useless? - end - n - end + @n_useless_rules ||= each_useless_rule.count + end + + def each_useless_rule + return to_enum __method__ unless block_given? + + each do |r| + yield r if r.useless? + end end def nfa diff --git a/test/assets/ifelse.y b/test/assets/ifelse.y new file mode 100644 index 00000000..18dbe4b1 --- /dev/null +++ b/test/assets/ifelse.y @@ -0,0 +1,14 @@ +class C::Parser +token tSOMETHING +rule + statement + : tSOMETHING + | 'if' statement 'then' statement + | 'if' statement 'then' statement 'else' statement + ; + + dummy + : tSOMETHING '+' tSOMETHING + | tSOMETHING '-' tSOMETHING + ; + diff --git a/test/test_racc_command.rb b/test/test_racc_command.rb index b4fc0c67..f1116a37 100644 --- a/test/test_racc_command.rb +++ b/test/test_racc_command.rb @@ -318,5 +318,20 @@ def test_tp_plus assert_debugfile 'tp_plus.y', [21, 0, 0, 0] assert_output_unchanged 'tp_plus.y' end + + def test_ifelse + stderr = nil + racc "-o#{@TAB_DIR}/ifelse", "#{ASSET_DIR}/ifelse.y", stdout_filter: ->(s) { stderr = s } + stderr = stderr.lines[1..-1].join if RUBY_PLATFORM.match? /java/ + assert_equal(<<~STDERR, stderr) + 1 useless nonterminals: + dummy + 2 useless rules: + #4 (dummy) + #5 (dummy) + 1 shift/reduce conflicts + Turn on logging with "-v" and check ".output" file for details + STDERR + end end end