Skip to content

Commit

Permalink
Implement the -W and --warnings options
Browse files Browse the repository at this point in the history
  • Loading branch information
ydah committed Jun 18, 2024
1 parent bed4527 commit 63d32bf
Show file tree
Hide file tree
Showing 16 changed files with 425 additions and 231 deletions.
4 changes: 3 additions & 1 deletion lib/lrama.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
require_relative "lrama/command"
require_relative "lrama/context"
require_relative "lrama/counterexamples"
require_relative "lrama/diagnostics"
require_relative "lrama/digraph"
require_relative "lrama/grammar"
require_relative "lrama/grammar_validator"
require_relative "lrama/lexer"
require_relative "lrama/logger"
require_relative "lrama/option_parser"
require_relative "lrama/options"
require_relative "lrama/output"
Expand All @@ -17,4 +20,3 @@
require_relative "lrama/states_reporter"
require_relative "lrama/trace_reporter"
require_relative "lrama/version"
require_relative "lrama/warning"
9 changes: 4 additions & 5 deletions lib/lrama/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def run(argv)

Report::Duration.enable if options.trace_opts[:time]

warning = Lrama::Warning.new
text = options.y.read
options.y.close if options.y != STDIN
begin
Expand All @@ -33,7 +32,7 @@ def run(argv)
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
abort message
end
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states.compute
context = Lrama::Context.new(states)

Expand All @@ -60,9 +59,9 @@ def run(argv)
).render
end

if warning.has_error?
exit false
end
logger = Lrama::Logger.new
exit false unless Lrama::GrammarValidator.new(grammar, states, logger).valid?
Lrama::Diagnostics.new(states, logger).run(**options.diagnostic_opts)
end
end
end
26 changes: 26 additions & 0 deletions lib/lrama/diagnostics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Lrama
class Diagnostics
def initialize(states, logger)
@states = states
@logger = logger
end

def run(conflicts_sr: false, conflicts_rr: false)
diagnose_conflict(conflicts_sr, conflicts_rr)
end

private

def diagnose_conflict(conflicts_sr, conflicts_rr)
if conflicts_sr && @states.sr_conflicts_count != 0
@logger.warn("shift/reduce conflicts: #{@states.sr_conflicts_count} found")
end

if conflicts_rr && @states.rr_conflicts_count != 0
@logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found")
end
end
end
end
37 changes: 37 additions & 0 deletions lib/lrama/grammar_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Lrama
class GrammarValidator
def initialize(grammar, states, logger)
@grammar = grammar
@states = states
@logger = logger
end

def valid?
conflicts_within_threshold?
end

private

def conflicts_within_threshold?
return true unless @grammar.expect

[sr_conflicts_within_threshold(@grammar.expect), rr_conflicts_within_threshold(0)].all?
end

def sr_conflicts_within_threshold(expected)
return true if expected == @states.sr_conflicts_count

@logger.error("shift/reduce conflicts: #{@states.sr_conflicts_count} found, #{expected} expected")
false
end

def rr_conflicts_within_threshold(expected)
return true if expected == @states.rr_conflicts_count

@logger.error("reduce/reduce conflicts: #{@states.rr_conflicts_count} found, #{expected} expected")
false
end
end
end
16 changes: 3 additions & 13 deletions lib/lrama/warning.rb → lib/lrama/logger.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
# frozen_string_literal: true

module Lrama
class Warning
attr_reader :errors, :warns

class Logger
def initialize(out = STDERR)
@out = out
@errors = []
@warns = []
end

def error(message)
@out << message << "\n"
@errors << message
end

def warn(message)
@out << message << "\n"
@warns << message
end

def has_error?
!@errors.empty?
def error(message)
@out << message << "\n"
end
end
end
32 changes: 32 additions & 0 deletions lib/lrama/option_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def parse(argv)

@options.trace_opts = validate_trace(@trace)
@options.report_opts = validate_report(@report)
@options.diagnostic_opts = validate_diagnostic(@diagnostic)
@options.grammar_file = argv.shift

if !@options.grammar_file
Expand Down Expand Up @@ -80,6 +81,15 @@ def parse_by_option_parser(argv)

o.on('-v', 'reserved, do nothing') { }
o.separator ''
o.separator 'Diagnostics:'
o.on('-W', '--warnings=CATEGORY', Array, 'report the warnings falling in category') {|v| @diagnostic = v }
o.separator ''
o.separator 'Warning categories include:'
o.separator ' conflicts-sr Shift/Reduce conflicts (enabled by default)'
o.separator ' conflicts-rr Reduce/Reduce conflicts (enabled by default)'
o.separator ' all all warnings'
o.separator ' none turn off all warnings'
o.separator ''
o.separator 'Error Recovery:'
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
o.separator ''
Expand Down Expand Up @@ -140,5 +150,27 @@ def validate_trace(trace)

return h
end

DIAGNOSTICS = %w[]
HYPHENATED_DIAGNOSTICS = %w[conflicts-sr conflicts-rr]

def validate_diagnostic(diagnostic)
h = { conflicts_sr: true, conflicts_rr: true }
return h if diagnostic.nil?
return {} if diagnostic.any? { |d| d == 'none' }
return { all: true } if diagnostic.any? { |d| d == 'all' }

diagnostic.each do |d|
if DIAGNOSTICS.include?(d)
h[d.to_sym] = true
elsif HYPHENATED_DIAGNOSTICS.include?(d)
h[d.gsub('-', '_').to_sym] = true
else
raise "Invalid diagnostic option \"#{d}\"."
end
end

return h
end
end
end
5 changes: 3 additions & 2 deletions lib/lrama/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class Options
attr_accessor :skeleton, :header, :header_file,
:report_file, :outfile,
:error_recovery, :grammar_file,
:trace_opts, :report_opts, :y,
:debug
:trace_opts, :report_opts,
:diagnostic_opts, :y, :debug

def initialize
@skeleton = "bison/yacc.c"
Expand All @@ -19,6 +19,7 @@ def initialize
@grammar_file = nil
@trace_opts = nil
@report_opts = nil
@diagnostic_opts = nil
@y = STDIN
@debug = false
end
Expand Down
44 changes: 7 additions & 37 deletions lib/lrama/states.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ class States

attr_reader :states, :reads_relation, :includes_relation, :lookback_relation

def initialize(grammar, warning, trace_state: false)
def initialize(grammar, trace_state: false)
@grammar = grammar
@warning = warning
@trace_state = trace_state

@states = []
Expand Down Expand Up @@ -91,8 +90,6 @@ def compute
report_duration(:compute_conflicts) { compute_conflicts }

report_duration(:compute_default_reduction) { compute_default_reduction }

check_conflicts
end

def reporter
Expand Down Expand Up @@ -127,16 +124,16 @@ def la
end
end

private

def sr_conflicts
@states.flat_map(&:sr_conflicts)
def sr_conflicts_count
@sr_conflicts_count ||= @states.flat_map(&:sr_conflicts).count
end

def rr_conflicts
@states.flat_map(&:rr_conflicts)
def rr_conflicts_count
@rr_conflicts_count ||= @states.flat_map(&:rr_conflicts).count
end

private

def trace_state
if @trace_state
yield STDERR
Expand Down Expand Up @@ -527,32 +524,5 @@ def compute_default_reduction
end.first
end
end

def check_conflicts
sr_count = sr_conflicts.count
rr_count = rr_conflicts.count

if @grammar.expect

expected_sr_conflicts = @grammar.expect
expected_rr_conflicts = 0

if expected_sr_conflicts != sr_count
@warning.error("shift/reduce conflicts: #{sr_count} found, #{expected_sr_conflicts} expected")
end

if expected_rr_conflicts != rr_count
@warning.error("reduce/reduce conflicts: #{rr_count} found, #{expected_rr_conflicts} expected")
end
else
if sr_count != 0
@warning.warn("shift/reduce conflicts: #{sr_count} found")
end

if rr_count != 0
@warning.warn("reduce/reduce conflicts: #{rr_count} found")
end
end
end
end
end
1 change: 1 addition & 0 deletions sig/lrama/options.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Lrama
attr_accessor grammar_file: String?
attr_accessor trace_opts: Hash[Symbol, bool]?
attr_accessor report_opts: Hash[Symbol, bool]?
attr_accessor diagnostic_opts: Hash[Symbol, bool]?
attr_accessor y: IO
attr_accessor debug: bool

Expand Down
9 changes: 3 additions & 6 deletions spec/lrama/context_spec.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
# frozen_string_literal: true

RSpec.describe Lrama::Context do
let(:out) { StringIO.new }
let(:warning) { Lrama::Warning.new(out) }

describe "basic" do
it do
path = "context/basic.y"
y = File.read(fixture_path(path))
grammar = Lrama::Parser.new(y, path).parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
context = Lrama::Context.new(states)

Expand Down Expand Up @@ -189,7 +186,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
context = Lrama::Context.new(states)

Expand Down Expand Up @@ -240,7 +237,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
context = Lrama::Context.new(states)

Expand Down
11 changes: 4 additions & 7 deletions spec/lrama/counterexamples_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# frozen_string_literal: true

RSpec.describe Lrama::Counterexamples do
let(:out) { StringIO.new }
let(:warning) { Lrama::Warning.new(out) }

describe "#compute" do
# Example comes from https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
# "4. Constructing Nonunifying Counterexamples"
Expand Down Expand Up @@ -55,7 +52,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
counterexamples = Lrama::Counterexamples.new(states)

Expand Down Expand Up @@ -256,7 +253,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
counterexamples = Lrama::Counterexamples.new(states)

Expand Down Expand Up @@ -335,7 +332,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
counterexamples = Lrama::Counterexamples.new(states)

Expand Down Expand Up @@ -418,7 +415,7 @@
grammar = Lrama::Parser.new(y, "parse.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, warning)
states = Lrama::States.new(grammar)
states.compute
counterexamples = Lrama::Counterexamples.new(states)

Expand Down
Loading

0 comments on commit 63d32bf

Please sign in to comment.