diff --git a/lib/lrama/command.rb b/lib/lrama/command.rb index 17aad1a1..b7042fca 100644 --- a/lib/lrama/command.rb +++ b/lib/lrama/command.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "stringio" + module Lrama class Command LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__))) @@ -33,8 +35,8 @@ def execute_command_workflow render_reports(states) if @options.report_file @tracer.trace(grammar) render_diagram(grammar) - render_output(context, grammar) states.validate!(@logger) + render_output(context, grammar) @warnings.warn(grammar, states) end @@ -103,18 +105,18 @@ def render_diagram(grammar) end def render_output(context, grammar) - File.open(@options.outfile, "w+") do |f| - Lrama::Output.new( - out: f, - output_file_path: @options.outfile, - template_name: @options.skeleton, - grammar_file_path: @options.grammar_file, - header_file_path: @options.header_file, - context: context, - grammar: grammar, - error_recovery: @options.error_recovery, - ).render - end + out = StringIO.new + Lrama::Output.new( + out: out, + output_file_path: @options.outfile, + template_name: @options.skeleton, + grammar_file_path: @options.grammar_file, + header_file_path: @options.header_file, + context: context, + grammar: grammar, + error_recovery: @options.error_recovery, + ).render + File.write(@options.outfile, out.string) end end end diff --git a/spec/lrama/command_spec.rb b/spec/lrama/command_spec.rb index 58069e4a..7e20cebe 100644 --- a/spec/lrama/command_spec.rb +++ b/spec/lrama/command_spec.rb @@ -81,5 +81,49 @@ File.delete("report.output") end end + + context "when conflicts do not match %expect" do + it "does not create the parser output" do + grammar = <<~Y + %expect 0 + %token NUM + %% + expr: NUM | expr '+' expr; + Y + + Dir.mktmpdir do |dir| + outfile = File.join(dir, "parse.c") + allow(STDIN).to receive(:read).and_return(grammar) + command = Lrama::Command.new(["-o", outfile, "-", "conflict.y"]) + errors = StringIO.new + command.instance_variable_set(:@logger, Lrama::Logger.new(errors)) + + expect { command.run }.to raise_error(SystemExit) + expect(errors.string).to eq("error: shift/reduce conflicts: 1 found, 0 expected\n") + expect(File).not_to exist(outfile) + end + end + end + + context "when rendering fails" do + it "preserves the existing parser output" do + Dir.mktmpdir do |dir| + outfile = File.join(dir, "parse.c") + File.write(outfile, "existing output") + command = Lrama::Command.new(["-o", outfile, fixture_path("command/basic.y")]) + allow(Lrama::Output).to receive(:new) do |out:, **| + output = instance_double(Lrama::Output) + allow(output).to receive(:render) do + out << "partial output" + raise "render failed" + end + output + end + + expect { command.run }.to raise_error("render failed") + expect(File.read(outfile)).to eq("existing output") + end + end + end end end