diff --git a/src/ChibiRuby.Cli/Program.cs b/src/ChibiRuby.Cli/Program.cs index 67312ba..9187ab5 100644 --- a/src/ChibiRuby.Cli/Program.cs +++ b/src/ChibiRuby.Cli/Program.cs @@ -31,47 +31,59 @@ public void Compile( var state = MRubyState.Create(); var inputBytes = File.ReadAllBytes(inputFile); - if (dump) + try { - Irep irep; - if (IsBytecode(inputFile, inputBytes)) + if (dump) { - irep = state.ParseBytecode(inputBytes); + Irep irep; + if (IsBytecode(inputFile, inputBytes)) + { + irep = state.ParseBytecode(inputBytes); + } + else + { + var compiler = MRubyCompiler.Create(state); + using var compilation = compiler.Compile(inputBytes); + irep = state.ParseBytecode(compilation.AsBytecode()); + } + + var bufferWriter = new ArrayBufferWriter(); + DumpIrepRecursive(state, irep, bufferWriter); + + using var outputStream = output is null or "-" + ? Console.OpenStandardOutput() + : File.Create(output); + outputStream.Write(bufferWriter.WrittenSpan); } else { var compiler = MRubyCompiler.Create(state); using var compilation = compiler.Compile(inputBytes); - irep = state.ParseBytecode(compilation.AsBytecode()); - } - var bufferWriter = new ArrayBufferWriter(); - DumpIrepRecursive(state, irep, bufferWriter); + // Resolve the bytecode before opening the destination so a compile error + // doesn't leave behind a truncated/empty output file. + var bytecode = compilation.AsBytecode().ToArray(); - using var outputStream = output is null or "-" - ? Console.OpenStandardOutput() - : File.Create(output); - outputStream.Write(bufferWriter.WrittenSpan); - } - else - { - var compiler = MRubyCompiler.Create(state); - using var compilation = compiler.Compile(inputBytes); + using var outputStream = output == "-" + ? Console.OpenStandardOutput() + : File.Create(output ?? GetDefaultOutputPath(inputFile, format)); - using var outputStream = output == "-" - ? Console.OpenStandardOutput() - : File.Create(output ?? GetDefaultOutputPath(inputFile, format)); - - switch (format) - { - case OutputFormat.binary: - outputStream.Write(compilation.AsBytecode()); - break; - case OutputFormat.csharp: - WriteCSharpOutput(outputStream, compilation.AsBytecode(), csharpNamespace, csharpClassName); - break; + switch (format) + { + case OutputFormat.binary: + outputStream.Write(bytecode); + break; + case OutputFormat.csharp: + WriteCSharpOutput(outputStream, bytecode, csharpNamespace, csharpClassName); + break; + } } } + catch (MRubyCompileException ex) + { + Console.Error.WriteLine($"{inputFile}: {ex.Message}"); + Environment.Exit(1); + } } static string GetDefaultOutputPath(string inputFile, OutputFormat format) diff --git a/src/ChibiRuby.Compiler/CompilationResult.cs b/src/ChibiRuby.Compiler/CompilationResult.cs index 3dbc25d..fc44290 100644 --- a/src/ChibiRuby.Compiler/CompilationResult.cs +++ b/src/ChibiRuby.Compiler/CompilationResult.cs @@ -49,9 +49,20 @@ internal CompilationResult( public unsafe ReadOnlySpan AsBytecode() { + if (HasError || bytecodeDataPtr == IntPtr.Zero || bytecodeLength <= 0) + { + throw new MRubyCompileException(FormatDiagnostics()); + } return new ReadOnlySpan((byte*)bytecodeDataPtr, bytecodeLength); } + string FormatDiagnostics() + { + return Diagnostics.Count > 0 + ? string.Join(Environment.NewLine, Diagnostics) + : "Ruby compilation failed (no diagnostics available)."; + } + public ReadOnlySpan AsSpan() => AsBytecode(); public Irep ToIrep() diff --git a/tests/ChibiRuby.Tests/CompilerTest.cs b/tests/ChibiRuby.Tests/CompilerTest.cs index 325d3a3..8f60c7c 100644 --- a/tests/ChibiRuby.Tests/CompilerTest.cs +++ b/tests/ChibiRuby.Tests/CompilerTest.cs @@ -51,6 +51,33 @@ public void BomValidation() Assert.Throws(() => compiler.LoadSourceCode(utf32WithBom)); } + [Test] + public void SyntaxError_ThrowsCompileExceptionWithDiagnostics() + { + // A bare endless range in a `when` clause is a syntax error (in CRuby too). + // The compiler must surface the diagnostic, not hand back empty bytecode that + // later blows up in the RiteParser with an opaque "Binary size is too short". + const string source = """ + case x + when 10.. + puts "hi" + end + """; + + var ex = Assert.Throws(() => compiler.LoadSourceCode(source)); + Assert.That(ex!.Message, Does.Contain("when")); + Assert.That(ex.Message, Does.Not.Contain("Binary size is too short")); + } + + [Test] + public void SyntaxError_CompilationResultReportsError() + { + using var compilation = compiler.Compile("when 10..\n puts 1\n"u8); + Assert.That(compilation.HasError, Is.True); + Assert.That(compilation.Diagnostics, Is.Not.Empty); + Assert.Throws(() => _ = compilation.AsBytecode().Length); + } + static byte[] Encode(string sourceCode, Encoding encoding) { using var ms = new MemoryStream();