Skip to content

Commit

Permalink
Merge pull request #448 from ydah/warn-option
Browse files Browse the repository at this point in the history
Introduce the `-W` and `--warnings` options and support redefined parameterizing rules
  • Loading branch information
ydah authored Jun 26, 2024
2 parents 11dce25 + 33e6440 commit cac17e2
Show file tree
Hide file tree
Showing 19 changed files with 418 additions and 232 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(grammar, states, logger).run(options.diagnostic)
end
end
end
36 changes: 36 additions & 0 deletions lib/lrama/diagnostics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

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

def run(diagnostic)
if diagnostic
diagnose_conflict
diagnose_parameterizing_redefined
end
end

private

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

if @states.rr_conflicts_count != 0
@logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found")
end
end

def diagnose_parameterizing_redefined
@grammar.parameterizing_rule_resolver.redefined_rules.each do |rule|
@logger.warn("parameterizing rule redefined: #{rule}")
end
end
end
end
2 changes: 1 addition & 1 deletion lib/lrama/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module Lrama
class Grammar
extend Forwardable

attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver
attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action,
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
:symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations
Expand Down
4 changes: 4 additions & 0 deletions lib/lrama/grammar/parameterizing_rule/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def created_lhs(lhs_s_value)
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
end

def redefined_rules
@rules.select { |rule| @rules.count { |r| r.name == rule.name && r.required_parameters_count == rule.required_parameters_count } > 1 }
end

private

def select_rules(rules, token)
Expand Down
4 changes: 4 additions & 0 deletions lib/lrama/grammar/parameterizing_rule/rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def initialize(name, parameters, rhs_list, tag: nil, is_inline: false)
@is_inline = is_inline
@required_parameters_count = parameters.count
end

def to_s
"#{@name}(#{@parameters.map(&:s_value).join(', ')})"
end
end
end
end
Expand Down
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
3 changes: 3 additions & 0 deletions lib/lrama/option_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def parse_by_option_parser(argv)

o.on('-v', 'reserved, do nothing') { }
o.separator ''
o.separator 'Diagnostics:'
o.on('-W', '--warnings', 'report the warnings') {|v| @options.diagnostic = true }
o.separator ''
o.separator 'Error Recovery:'
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
o.separator ''
Expand Down
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, :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 = false
@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: 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 cac17e2

Please sign in to comment.