diff --git a/lib/lrama.rb b/lib/lrama.rb index 501e65d5..fe2e0580 100644 --- a/lib/lrama.rb +++ b/lib/lrama.rb @@ -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" @@ -17,4 +20,3 @@ require_relative "lrama/states_reporter" require_relative "lrama/trace_reporter" require_relative "lrama/version" -require_relative "lrama/warning" diff --git a/lib/lrama/command.rb b/lib/lrama/command.rb index a09f95e6..0095c1a1 100644 --- a/lib/lrama/command.rb +++ b/lib/lrama/command.rb @@ -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 @@ -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) @@ -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 diff --git a/lib/lrama/diagnostics.rb b/lib/lrama/diagnostics.rb new file mode 100644 index 00000000..e9da398c --- /dev/null +++ b/lib/lrama/diagnostics.rb @@ -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 diff --git a/lib/lrama/grammar.rb b/lib/lrama/grammar.rb index 641932fc..3724f828 100644 --- a/lib/lrama/grammar.rb +++ b/lib/lrama/grammar.rb @@ -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 diff --git a/lib/lrama/grammar/parameterizing_rule/resolver.rb b/lib/lrama/grammar/parameterizing_rule/resolver.rb index edd7c918..fede5919 100644 --- a/lib/lrama/grammar/parameterizing_rule/resolver.rb +++ b/lib/lrama/grammar/parameterizing_rule/resolver.rb @@ -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) diff --git a/lib/lrama/grammar/parameterizing_rule/rule.rb b/lib/lrama/grammar/parameterizing_rule/rule.rb index e5dd3d5b..cc200d2f 100644 --- a/lib/lrama/grammar/parameterizing_rule/rule.rb +++ b/lib/lrama/grammar/parameterizing_rule/rule.rb @@ -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 diff --git a/lib/lrama/grammar_validator.rb b/lib/lrama/grammar_validator.rb new file mode 100644 index 00000000..77904995 --- /dev/null +++ b/lib/lrama/grammar_validator.rb @@ -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 diff --git a/lib/lrama/warning.rb b/lib/lrama/logger.rb similarity index 55% rename from lib/lrama/warning.rb rename to lib/lrama/logger.rb index d7567f42..e98eef0f 100644 --- a/lib/lrama/warning.rb +++ b/lib/lrama/logger.rb @@ -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 diff --git a/lib/lrama/option_parser.rb b/lib/lrama/option_parser.rb index c0c3f813..bff3ce6c 100644 --- a/lib/lrama/option_parser.rb +++ b/lib/lrama/option_parser.rb @@ -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 '' diff --git a/lib/lrama/options.rb b/lib/lrama/options.rb index b1f122b0..ccd76803 100644 --- a/lib/lrama/options.rb +++ b/lib/lrama/options.rb @@ -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" @@ -19,6 +19,7 @@ def initialize @grammar_file = nil @trace_opts = nil @report_opts = nil + @diagnostic = false @y = STDIN @debug = false end diff --git a/lib/lrama/states.rb b/lib/lrama/states.rb index 9b18759e..1de475bf 100644 --- a/lib/lrama/states.rb +++ b/lib/lrama/states.rb @@ -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 = [] @@ -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 @@ -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 @@ -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 diff --git a/sig/lrama/options.rbs b/sig/lrama/options.rbs index a186075a..d32dd675 100644 --- a/sig/lrama/options.rbs +++ b/sig/lrama/options.rbs @@ -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 diff --git a/spec/lrama/context_spec.rb b/spec/lrama/context_spec.rb index 312aa00c..6d2b6245 100644 --- a/spec/lrama/context_spec.rb +++ b/spec/lrama/context_spec.rb @@ -1,9 +1,6 @@ # 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" @@ -11,7 +8,7 @@ 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) @@ -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) @@ -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) diff --git a/spec/lrama/counterexamples_spec.rb b/spec/lrama/counterexamples_spec.rb index aad34b58..2be7240a 100644 --- a/spec/lrama/counterexamples_spec.rb +++ b/spec/lrama/counterexamples_spec.rb @@ -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" @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/spec/lrama/diagnostics_spec.rb b/spec/lrama/diagnostics_spec.rb new file mode 100644 index 00000000..87185662 --- /dev/null +++ b/spec/lrama/diagnostics_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +RSpec.describe Lrama::Diagnostics do + describe "#run" do + context "when rule has conflicts" do + let(:y) do + <<~STR + %{ + // Prologue + %} + + %union { + int i; + long l; + char *str; + } + + %token EOI 0 "EOI" + %token '\\' "backslash" + %token '\13' "escaped vertical tab" + %token keyword_class + %token keyword_class2 + %token tNUMBER + %token tSTRING + %token keyword_end "end" + %token tPLUS "+" + %token tMINUS "-" + %token tEQ "=" + %token tEQEQ "==" + + %type class /* comment for class */ + + %nonassoc tEQEQ + %left tPLUS tMINUS '>' + %right tEQ + + %% + + program: class + | '+' strings_1 + | '-' strings_2 + ; + + class : keyword_class tSTRING keyword_end %prec tPLUS + { code 1 } + | keyword_class { code 2 } tSTRING '!' keyword_end { code 3 } %prec "=" + | keyword_class { code 4 } tSTRING '?' keyword_end { code 5 } %prec '>' + ; + + strings_1: string_1 + ; + + strings_2: string_1 + | string_2 + ; + + string_1: string + ; + + string_2: string '+' + ; + + string: tSTRING + ; + + %% + STR + end + + it "has warns for s/r conflicts and r/r conflicts" do + grammar = Lrama::Parser.new(y, "states/check_conflicts.y").parse + grammar.prepare + grammar.validate! + states = Lrama::States.new(grammar) + states.compute + logger = Lrama::Logger.new + allow(logger).to receive(:warn) + Lrama::Diagnostics.new(grammar, states, logger).run(true) + expect(logger).to have_received(:warn).with("shift/reduce conflicts: 2 found") + expect(logger).to have_received(:warn).with("reduce/reduce conflicts: 1 found") + end + end + + context "when rule has parameterizing redefined" do + let(:y) do + <<~STR + %{ + // Prologue + %} + %union { + int i; + } + %token tNUMBER + %rule foo(X) : X + ; + %rule foo(Y) : Y + ; + %% + program: foo(tNUMBER) + ; + STR + end + + it "has warns for parameterizing redefined" do + grammar = Lrama::Parser.new(y, "states/parameterizing_rule_redefined.y").parse + grammar.prepare + grammar.validate! + states = Lrama::States.new(grammar) + states.compute + logger = Lrama::Logger.new + allow(logger).to receive(:warn) + Lrama::Diagnostics.new(grammar, states, logger).run(true) + expect(logger).to have_received(:warn).with("parameterizing rule redefined: foo(X)") + expect(logger).to have_received(:warn).with("parameterizing rule redefined: foo(Y)") + end + end + end +end diff --git a/spec/lrama/grammar_validator_spec.rb b/spec/lrama/grammar_validator_spec.rb new file mode 100644 index 00000000..702ee5cf --- /dev/null +++ b/spec/lrama/grammar_validator_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +RSpec.describe Lrama::GrammarValidator do + describe "#valid?" do + let(:y) do + <<~STR + %union { + int i; + long l; + char *str; + } + + %token EOI 0 "EOI" + %token '\\' "backslash" + %token '\13' "escaped vertical tab" + %token keyword_class + %token keyword_class2 + %token tNUMBER + %token tSTRING + %token keyword_end "end" + %token tPLUS "+" + %token tMINUS "-" + %token tEQ "=" + %token tEQEQ "==" + + %type class /* comment for class */ + + %nonassoc tEQEQ + %left tPLUS tMINUS '>' + %right tEQ + + %% + + program: class + | '+' strings_1 + | '-' strings_2 + ; + + class : keyword_class tSTRING keyword_end %prec tPLUS + { code 1 } + | keyword_class { code 2 } tSTRING '!' keyword_end { code 3 } %prec "=" + | keyword_class { code 4 } tSTRING '?' keyword_end { code 5 } %prec '>' + ; + + strings_1: string_1 + ; + + strings_2: string_1 + | string_2 + ; + + string_1: string + ; + + string_2: string '+' + ; + + string: tSTRING + ; + + %% + STR + end + + context "when expect is specified" do + context "when the number of s/r conflicts is same with expect" do + let(:header) do + <<~STR + %{ + // Prologue + %} + + %expect 2 + STR + end + + it "has errors for r/r conflicts" do + grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse + grammar.prepare + grammar.validate! + states = Lrama::States.new(grammar) + states.compute + logger = Lrama::Logger.new + allow(logger).to receive(:error) + + expect(Lrama::GrammarValidator.new(grammar, states, logger).valid?).to eq(false) + expect(logger).to have_received(:error).with("reduce/reduce conflicts: 1 found, 0 expected") + end + end + + context "when the number of s/r conflicts is not same with expect" do + let(:header) do + <<~STR + %{ + // Prologue + %} + + %expect 0 + STR + end + + it "has errors for s/r conflicts and r/r conflicts" do + grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse + grammar.prepare + grammar.validate! + states = Lrama::States.new(grammar) + states.compute + logger = Lrama::Logger.new + allow(logger).to receive(:error) + + expect(Lrama::GrammarValidator.new(grammar, states, logger).valid?).to eq(false) + expect(logger).to have_received(:error).with("shift/reduce conflicts: 2 found, 0 expected") + expect(logger).to have_received(:error).with("reduce/reduce conflicts: 1 found, 0 expected") + end + end + end + + describe "expect is not specified" do + let(:header) do + <<~STR + %{ + // Prologue + %} + STR + end + + it "has warns for s/r conflicts and r/r conflicts" do + grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse + grammar.prepare + grammar.validate! + states = Lrama::States.new(grammar) + states.compute + logger = Lrama::Logger.new + allow(logger).to receive(:error) + + expect(Lrama::GrammarValidator.new(grammar, states, logger).valid?).to eq(true) + expect(logger).not_to have_received(:error) + end + end + end + end diff --git a/spec/lrama/option_parser_spec.rb b/spec/lrama/option_parser_spec.rb index e4d7ed98..969ccd30 100644 --- a/spec/lrama/option_parser_spec.rb +++ b/spec/lrama/option_parser_spec.rb @@ -60,6 +60,9 @@ --trace=THINGS also output trace logs at runtime -v reserved, do nothing + Diagnostics: + -W, --warnings report the warnings + Error Recovery: -e enable error recovery @@ -236,4 +239,35 @@ end end end + + describe "@diagnostic" do + context "when diagnostic option is not passed" do + it "returns false" do + option_parser = Lrama::OptionParser.new + option_parser.send(:parse, [fixture_path("command/basic.y")]) + options = option_parser.instance_variable_get(:@options) + expect(options.diagnostic).to be false + end + end + + context "when diagnostic option is passed" do + context "when --warnings is passed" do + it "returns true" do + option_parser = Lrama::OptionParser.new + option_parser.send(:parse, ["--warnings", fixture_path("command/basic.y")]) + options = option_parser.instance_variable_get(:@options) + expect(options.diagnostic).to be true + end + end + + context "when -W is passed" do + it "returns true" do + option_parser = Lrama::OptionParser.new + option_parser.send(:parse, ["-W", fixture_path("command/basic.y")]) + options = option_parser.instance_variable_get(:@options) + expect(options.diagnostic).to be true + end + end + end + end end diff --git a/spec/lrama/output_spec.rb b/spec/lrama/output_spec.rb index 6fdbd196..e70a1012 100644 --- a/spec/lrama/output_spec.rb +++ b/spec/lrama/output_spec.rb @@ -17,7 +17,6 @@ } let(:out) { StringIO.new } let(:header_out) { StringIO.new } - let(:warning) { Lrama::Warning.new(StringIO.new) } # suppress warnings let(:text) { File.read(grammar_file_path) } let(:grammar) do grammar = Lrama::Parser.new(text, grammar_file_path).parse @@ -25,7 +24,7 @@ grammar.validate! grammar end - let(:states) { s = Lrama::States.new(grammar, warning); s.compute; s } + let(:states) { s = Lrama::States.new(grammar); s.compute; s } let(:context) { Lrama::Context.new(states) } let(:grammar_file_path) { fixture_path("common/basic.y") } let(:header_file_path) { "y.tab.h" } diff --git a/spec/lrama/states_spec.rb b/spec/lrama/states_spec.rb index 0d7b115e..953ac54c 100644 --- a/spec/lrama/states_spec.rb +++ b/spec/lrama/states_spec.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true RSpec.describe Lrama::States do - let(:out) { StringIO.new } - let(:warning) { Lrama::Warning.new(out) } - describe '#compute' do it "basic" do path = "common/basic.y" @@ -11,7 +8,7 @@ grammar = Lrama::Parser.new(y, path).parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -329,7 +326,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, path).parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute expect(states.states.map(&:accessing_symbol)).to eq([ @@ -372,7 +369,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, path).parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -642,7 +639,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, path).parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -943,7 +940,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_look_ahead_sets.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1014,7 +1011,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_look_ahead_sets.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1106,7 +1103,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_conflicts.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1225,7 +1222,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_conflicts.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1324,7 +1321,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_conflicts.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1475,7 +1472,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_default_reduction.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1649,7 +1646,7 @@ class go to state 5 grammar = Lrama::Parser.new(y, "states/compute_default_reduction.y").parse grammar.prepare grammar.validate! - states = Lrama::States.new(grammar, warning) + states = Lrama::States.new(grammar) states.compute io = StringIO.new @@ -1790,148 +1787,4 @@ class go to state 5 STR end end - - describe "#check_conflicts" do - describe "the rule has conflicts" do - let(:y) do - <<~STR - %union { - int i; - long l; - char *str; - } - - %token EOI 0 "EOI" - %token '\\' "backslash" - %token '\13' "escaped vertical tab" - %token keyword_class - %token keyword_class2 - %token tNUMBER - %token tSTRING - %token keyword_end "end" - %token tPLUS "+" - %token tMINUS "-" - %token tEQ "=" - %token tEQEQ "==" - - %type class /* comment for class */ - - %nonassoc tEQEQ - %left tPLUS tMINUS '>' - %right tEQ - - %% - - program: class - | '+' strings_1 - | '-' strings_2 - ; - - class : keyword_class tSTRING keyword_end %prec tPLUS - { code 1 } - | keyword_class { code 2 } tSTRING '!' keyword_end { code 3 } %prec "=" - | keyword_class { code 4 } tSTRING '?' keyword_end { code 5 } %prec '>' - ; - - strings_1: string_1 - ; - - strings_2: string_1 - | string_2 - ; - - string_1: string - ; - - string_2: string '+' - ; - - string: tSTRING - ; - - %% - STR - end - - describe "expect is specified" do - describe "the number of s/r conflicts is same with expect" do - let(:header) do - <<~STR - %{ - // Prologue - %} - - %expect 2 - STR - end - - it "has errors for r/r conflicts" do - grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse - grammar.prepare - grammar.validate! - states = Lrama::States.new(grammar, warning) - states.compute - - expect(out.string).to eq("reduce/reduce conflicts: 1 found, 0 expected\n") - expect(warning.errors).to eq([ - "reduce/reduce conflicts: 1 found, 0 expected" - ]) - expect(warning.warns).to eq([]) - end - end - - describe "the number of s/r conflicts is not same with expect" do - let(:header) do - <<~STR - %{ - // Prologue - %} - - %expect 0 - STR - end - - it "has errors for s/r conflicts and r/r conflicts" do - grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse - grammar.prepare - grammar.validate! - states = Lrama::States.new(grammar, warning) - states.compute - - expect(out.string).to eq("shift/reduce conflicts: 2 found, 0 expected\nreduce/reduce conflicts: 1 found, 0 expected\n") - expect(warning.errors).to eq([ - "shift/reduce conflicts: 2 found, 0 expected", - "reduce/reduce conflicts: 1 found, 0 expected" - ]) - expect(warning.warns).to eq([]) - end - end - end - - describe "expect is not specified" do - let(:header) do - <<~STR - %{ - // Prologue - %} - STR - end - - it "has warns for s/r conflicts and r/r conflicts" do - grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse - grammar.prepare - grammar.validate! - states = Lrama::States.new(grammar, warning) - states.compute - - expect(out.string).to eq("shift/reduce conflicts: 2 found\nreduce/reduce conflicts: 1 found\n") - expect(warning.errors).to eq([]) - expect(warning.warns).to eq([ - "shift/reduce conflicts: 2 found", - "reduce/reduce conflicts: 1 found" - ]) - end - end - end - end end