Add command line switch to output warnings to stderr - close #26

This commit is contained in:
Josh Holtrop 2024-07-14 15:31:43 -04:00
parent 4ae5ab79b3
commit 2dd89445fc
5 changed files with 44 additions and 16 deletions

View File

@ -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

View File

@ -4,15 +4,21 @@ class Propane
USAGE = <<EOF
Usage: #{$0} [options] <input-file> <output-file>
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

View File

@ -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.

View File

@ -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

View File

@ -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 <<EOF
token a;
token b;
Start -> 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|