Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the -W and --warnings options and support redefined parameterizing rules #448

Merged
merged 3 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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