From 2dd89445fce2b4b1f3e304e0174e321f1a63aad7 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Sun, 14 Jul 2024 15:31:43 -0400 Subject: [PATCH] Add command line switch to output warnings to stderr - close #26 --- lib/propane.rb | 4 ++-- lib/propane/cli.rb | 16 ++++++++++++---- lib/propane/generator.rb | 5 +++-- lib/propane/parser.rb | 18 +++++++++++------- spec/propane_spec.rb | 17 ++++++++++++++++- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/lib/propane.rb b/lib/propane.rb index 33b2b76..715c706 100644 --- a/lib/propane.rb +++ b/lib/propane.rb @@ -31,10 +31,10 @@ class Propane class << self - def run(input_file, output_file, log_file) + def run(input_file, output_file, log_file, options) begin grammar = Grammar.new(File.read(input_file)) - generator = Generator.new(grammar, output_file, log_file) + generator = Generator.new(grammar, output_file, log_file, options) generator.generate rescue Error => e $stderr.puts e.message diff --git a/lib/propane/cli.rb b/lib/propane/cli.rb index ce205ee..46a5542 100644 --- a/lib/propane/cli.rb +++ b/lib/propane/cli.rb @@ -4,15 +4,21 @@ class Propane USAGE = < Options: - --log LOG Write log file - --version Show program version and exit - -h, --help Show this usage and exit + --log LOG Write log file. This will show all parser states and their + associated shifts and reduces. It can be helpful when + debugging a grammar. + --version Show program version and exit. + -h, --help Show this usage and exit. + -w Treat warnings as errors. This option will treat shift/reduce + conflicts as fatal errors and will print them to stderr in + addition to the log file. EOF class << self def run(args) params = [] + options = {} log_file = nil i = 0 while i < args.size @@ -29,6 +35,8 @@ EOF when "-h", "--help" puts USAGE return 0 + when "-w" + options[:warnings_as_errors] = true when /^-/ $stderr.puts "Error: unknown option #{arg}" return 1 @@ -45,7 +53,7 @@ EOF $stderr.puts "Error: cannot read #{params[0]}" return 2 end - Propane.run(*params, log_file) + Propane.run(*params, log_file, options) end end diff --git a/lib/propane/generator.rb b/lib/propane/generator.rb index 7611ad9..21c5923 100644 --- a/lib/propane/generator.rb +++ b/lib/propane/generator.rb @@ -2,7 +2,7 @@ class Propane class Generator - def initialize(grammar, output_file, log_file) + def initialize(grammar, output_file, log_file, options) @grammar = grammar @output_file = output_file if log_file @@ -16,6 +16,7 @@ class Propane else "d" end + @options = options process_grammar! end @@ -129,7 +130,7 @@ class Propane # Generate the lexer. @lexer = Lexer.new(@grammar) # Generate the parser. - @parser = Parser.new(@grammar, rule_sets, @log) + @parser = Parser.new(@grammar, rule_sets, @log, @options) end # Check that any referenced ptypes have been defined. diff --git a/lib/propane/parser.rb b/lib/propane/parser.rb index 0874a52..7228cb4 100644 --- a/lib/propane/parser.rb +++ b/lib/propane/parser.rb @@ -7,13 +7,14 @@ class Propane attr_reader :reduce_table attr_reader :rule_sets - def initialize(grammar, rule_sets, log) + def initialize(grammar, rule_sets, log, options) @grammar = grammar @rule_sets = rule_sets @log = log @item_sets = [] @item_sets_set = {} - @sr_conflicts = Set.new + @warnings = Set.new + @options = options start_item = Item.new(grammar.rules.first, 0) eval_item_sets = Set[ItemSet.new([start_item])] @@ -41,6 +42,9 @@ class Propane build_follow_sets! build_tables! write_log! + if @warnings.size > 0 && @options[:warnings_as_errors] + raise Error.new("Fatal errors (-w):\n" + @warnings.join("\n")) + end end private @@ -65,7 +69,7 @@ class Propane if item_set.reduce_actions shift_entries.each do |shift_entry| if item_set.reduce_actions.include?(shift_entry[:symbol]) - @sr_conflicts << [shift_entry[:symbol], item_set.reduce_actions[shift_entry[:symbol]]] + @warnings << "Shift/Reduce conflict between token #{shift_entry[:symbol].name} and rule #{item_set.reduce_actions[shift_entry[:symbol]].name}" end end end @@ -318,11 +322,11 @@ class Propane end end end - if @sr_conflicts.size > 0 + if @warnings.size > 0 @log.puts - @log.puts "Shift/Reduce Conflicts:" - @sr_conflicts.each do |sr_conflict| - @log.puts " Shift/Reduce conflict between #{sr_conflict[0].name} and #{sr_conflict[1].name}" + @log.puts "Warnings:" + @warnings.each do |warning| + @log.puts " #{warning}" end end end diff --git a/spec/propane_spec.rb b/spec/propane_spec.rb index f26b482..48cc0c8 100644 --- a/spec/propane_spec.rb +++ b/spec/propane_spec.rb @@ -54,6 +54,7 @@ EOF else command += %W[spec/run/testparser#{options[:name]}.propane spec/run/testparser#{options[:name]}.#{options[:language]} --log spec/run/testparser#{options[:name]}.log] end + command += (options[:extra_args] || []) if (options[:capture]) stdout, stderr, status = Open3.capture3(*command) Results.new(stdout, stderr, status) @@ -195,7 +196,21 @@ EOF results = run_propane(capture: true) expect(results.stderr).to eq "" expect(results.status).to eq 0 - expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict between b and As2} + expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict between token b and rule As2} + end + + it "errors on shift/reduce conflicts with -w" do + write_grammar < As? b?; +As -> a As2?; +As2 -> b a As2?; +EOF + results = run_propane(extra_args: %w[-w], capture: true) + expect(results.stderr).to match %r{Fatal errors \(-w\).*Shift/Reduce conflict between token b and rule As2}m + expect(results.status).to_not eq 0 + expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict between token b and rule As2} end %w[d c].each do |language|