From 779de9a53076c62af1f674ab2827648f2b2ca85b Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 9 Jul 2026 12:48:51 +0900 Subject: [PATCH] Rename binding.irb to binding.break and improve docs --- README.md | 30 ++++++++++++- editor-extensions/vscode/README.md | 4 +- editor-extensions/vscode/examples/sample.rb | 4 +- sandbox/SampleDebuggerEmbedded/Program.cs | 2 +- .../SampleDebuggerEmbedded/scenarios/quest.rb | 8 ++-- .../MRubyDapMessageHandler.cs | 8 ++-- .../ChibiRuby.Debugger.csproj | 2 +- src/ChibiRuby.Debugger/MRubyDebugger.cs | 6 +-- src/ChibiRuby.Debugger/StopReason.cs | 2 +- src/ChibiRuby/IMRubyDebuggerHook.cs | 4 +- src/ChibiRuby/MRubyState.Binding.cs | 2 +- src/ChibiRuby/MRubyState.Init.cs | 4 +- src/ChibiRuby/StdLib/BindingMembers.cs | 21 ++++++---- .../ChibiRubyDapServerTest.cs | 10 ++--- .../StackTraceLineTest.cs | 6 +-- .../TcpDapServerTest.cs | 8 ++-- .../MRubyDebuggerTest.cs | 42 +++++++++---------- tests/ChibiRuby.Tests/BindingTest.cs | 34 +++++++++++---- 18 files changed, 127 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index cd6ffbd0..451ee3e0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ Please refer to the following for the [benchmark code](https://github.com/hadash | ChibiRuby.Compiler | Ruby source compiler utility (native binding). | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Compiler)](https://www.nuget.org/packages/ChibiRuby.Compiler) | | ChibiRuby.Cli | dotnet tool with subcommands (e.g. `compile`) for ChibiRuby workflows | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Cli)](https://www.nuget.org/packages/ChibiRuby.Cli) | | ChibiRuby.Serializer | Converts between Ruby and C# objects | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Serializer)](https://www.nuget.org/packages/ChibiRuby.Serializer) | -| ChibiRuby.Debugger | Protocol-agnostic debugger core (breakpoints, stepping, `binding.irb` suspension) | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Debugger)](https://www.nuget.org/packages/ChibiRuby.Debugger) | +| ChibiRuby.Debugger | Protocol-agnostic debugger core (breakpoints, stepping, `binding.break` suspension) | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Debugger)](https://www.nuget.org/packages/ChibiRuby.Debugger) | | ChibiRuby.Debugger.Dap | DAP server (TCP) for any DAP-compatible editor — see [Debugger](#debugger) | [![NuGet](https://img.shields.io/nuget/v/ChibiRuby.Debugger.Dap)](https://www.nuget.org/packages/ChibiRuby.Debugger.Dap) | ### Unity @@ -1599,6 +1599,10 @@ VSCode and Zed need a small extension to register the `chibiruby` debug type; Ri ### Setting breakpoints +There are two ways to pause execution: set a breakpoint from the editor, or write `binding.break` directly in your Ruby code. + +#### From the editor + Once host + editor are wired: 1. Open the `.rb` file in the editor. @@ -1606,6 +1610,30 @@ Once host + editor are wired: 3. Run the host. Execution stops at the breakpoint; the editor surfaces the call stack and locals. 4. Use the editor's debug controls (**Continue** / **Step Over** / **Step In** / **Step Out**) and the REPL pane (variables view + expression evaluation) as usual. +#### From code (`binding.break`) + +Write `binding.break` at the line you want to pause at — same API as [ruby/debug](https://github.com/ruby/debug): + +```ruby +def update + hp = @hp + binding.break # execution suspends here; inspect `hp` from the editor + hp - 1 +end +``` + +`binding.b` and `debugger` are also available as aliases: + +```ruby +binding.b # same as binding.break +debugger # same as binding.break on the caller's frame +``` + +When the VM hits `binding.break`, the thread suspends until a DAP client attaches and resumes it — handy for stopping at a precise point before you've had a chance to attach the editor. Once a client has attached and disconnected, later `binding.break` calls become no-ops so the host doesn't hang after the editor goes away. + +> [!NOTE] +> `binding.break` requires the debugger to be installed on the state (`MRubyDapServer` does this automatically). Without it, calling `binding.break` raises `RuntimeError`. + > [!NOTE] > Please ensure that ChibiRubyCompiler passes the filename when compiling Ruby. > APIs such as CompileFile or Unity's ScriptedImporter resolve the filename automatically. diff --git a/editor-extensions/vscode/README.md b/editor-extensions/vscode/README.md index e56bf745..5068e412 100644 --- a/editor-extensions/vscode/README.md +++ b/editor-extensions/vscode/README.md @@ -20,7 +20,7 @@ Or search **"ChibiRuby Debugger"** in the Extensions panel. - Evaluate Ruby expressions in the suspended binding (Debug Console / Watch / Hover) - View `self` and locals in the Variables pane - Continue / terminate -- `binding.irb` as an inline pause (also supported, useful when you can't easily +- `binding.break` as an inline pause (also supported, useful when you can't easily set a gutter breakpoint — e.g. generated code) Not yet supported: launch mode (run a `.rb` file from VSCode), step @@ -33,7 +33,7 @@ over/into/out. The extension supports **attach mode** only. Your C# / Unity host embeds ChibiRuby and opens a TCP listener via `new MRubyDapServer(...).StartAsync()`; VSCode connects over TCP, sends your breakpoints, and execution halts when a -breakpoint hits (or at any `binding.irb` call). +breakpoint hits (or at any `binding.break` call). See [`sandbox/SampleDebuggerEmbedded/`](../../sandbox/SampleDebuggerEmbedded/) for a complete working host you can run with `dotnet run`. diff --git a/editor-extensions/vscode/examples/sample.rb b/editor-extensions/vscode/examples/sample.rb index 59d7c6c0..870d34c5 100644 --- a/editor-extensions/vscode/examples/sample.rb +++ b/editor-extensions/vscode/examples/sample.rb @@ -1,6 +1,6 @@ # Sample script for trying the ChibiRuby DAP debugger. # Open this file in VSCode (with the chibiruby-debugger extension installed), -# press F5, and you should pause at the binding.irb call. +# press F5, and you should pause at the binding.break call. greeting = "hello" counter = 0 @@ -9,7 +9,7 @@ counter += i end -binding.irb +binding.break # In the VSCode Debug Console, try: # greeting.upcase diff --git a/sandbox/SampleDebuggerEmbedded/Program.cs b/sandbox/SampleDebuggerEmbedded/Program.cs index f26506b8..1b6643b6 100644 --- a/sandbox/SampleDebuggerEmbedded/Program.cs +++ b/sandbox/SampleDebuggerEmbedded/Program.cs @@ -1,6 +1,6 @@ // Minimal embedded host that demonstrates how to wire up ChibiRuby.Debugger.Dap from a // regular C# application. Run with `dotnet run --project sandbox/SampleDebuggerEmbedded` -// and the program will block at the first binding.irb call until a DAP client (e.g. +// and the program will block at the first binding.break call until a DAP client (e.g. // VSCode with the chibiruby-debugger extension) attaches to 127.0.0.1:4711. using System.Net; diff --git a/sandbox/SampleDebuggerEmbedded/scenarios/quest.rb b/sandbox/SampleDebuggerEmbedded/scenarios/quest.rb index 1b586cb3..6680e02f 100644 --- a/sandbox/SampleDebuggerEmbedded/scenarios/quest.rb +++ b/sandbox/SampleDebuggerEmbedded/scenarios/quest.rb @@ -1,7 +1,7 @@ # Sample mruby script for poking at the embedded-host debugger. # # The host (Program.cs) loads this file via MRubyCompiler.LoadSourceCode. When the -# binding.irb line is reached, execution suspends until a DAP client attaches to the +# binding.break line is reached, execution suspends until a DAP client attaches to the # host's listening port (default 4711) and sends a `continue`. class Hero @@ -23,13 +23,13 @@ def take_damage(amount) hero.take_damage(15) -p "Before binding.irb: #{hero.name}, HP=#{hero.hp}" -binding.irb +p "Before binding.break: #{hero.name}, HP=#{hero.hp}" +binding.break # Try in the Debug Console: # self # the toplevel object # binding.local_variables # [:hero, :weapon, :inventory] # binding.local_variable_get(:weapon).upcase # binding.local_variable_get(:hero).hp -p "After binding.irb: #{hero.name}, HP=#{hero.hp}" +p "After binding.break: #{hero.name}, HP=#{hero.hp}" inventory diff --git a/src/ChibiRuby.Debugger.Dap/MRubyDapMessageHandler.cs b/src/ChibiRuby.Debugger.Dap/MRubyDapMessageHandler.cs index 3748a3b1..2484536d 100644 --- a/src/ChibiRuby.Debugger.Dap/MRubyDapMessageHandler.cs +++ b/src/ChibiRuby.Debugger.Dap/MRubyDapMessageHandler.cs @@ -29,7 +29,7 @@ enum HandshakePhase public MRubyDebugger Debugger { get; } const int ThreadId = 1; - const int FrameIdBindingIrb = 1; + const int FrameIdBindingBreak = 1; const int VariablesRefLocals = 1000; readonly PipeReader reader; @@ -211,7 +211,7 @@ await RespondSuccessAsync(new ConfigurationDoneResponse await HandleStepAsync(seq, "stepOut", StepMode.StepOut, cancellationToken).ConfigureAwait(false); break; case PauseRequest: - await RespondErrorAsync(seq, "pause", "pause is not supported in Phase 1; use binding.irb", cancellationToken).ConfigureAwait(false); + await RespondErrorAsync(seq, "pause", "pause is not supported in Phase 1; use binding.break", cancellationToken).ConfigureAwait(false); break; case DisconnectRequest: await HandleDisconnectAsync(seq, "disconnect", cancellationToken).ConfigureAwait(false); @@ -377,7 +377,7 @@ Task HandleStackTraceAsync(int seq, CancellationToken cancellationToken) var frame = new StackFrame { - Id = FrameIdBindingIrb, + Id = FrameIdBindingBreak, Name = sourcePath is null ? "(toplevel)" : Path.GetFileName(sourcePath), Line = (ulong)line, Column = 1, @@ -617,7 +617,7 @@ Task SendStoppedEventAsync(StopEvent stopEvent) StopReason.LineBreakpoint => ("breakpoint", stopEvent.Line > 0 ? $"Paused at line breakpoint {Path.GetFileName(stopEvent.File ?? "")}:{stopEvent.Line}" : "Paused at line breakpoint"), - StopReason.BindingIrb => ("pause", "Paused at binding.irb"), + StopReason.BindingBreak => ("pause", "Paused at binding.break"), StopReason.Step => ("step", stopEvent.Line > 0 ? $"Paused after step at {Path.GetFileName(stopEvent.File ?? "")}:{stopEvent.Line}" : "Paused after step"), diff --git a/src/ChibiRuby.Debugger/ChibiRuby.Debugger.csproj b/src/ChibiRuby.Debugger/ChibiRuby.Debugger.csproj index 0fced682..d90f4187 100644 --- a/src/ChibiRuby.Debugger/ChibiRuby.Debugger.csproj +++ b/src/ChibiRuby.Debugger/ChibiRuby.Debugger.csproj @@ -4,7 +4,7 @@ net8.0;net9.0;net10.0;netstandard2.1 enable 13 - Debugger core for ChibiRuby. Protocol-agnostic primitives for breakpoints, stepping, and binding.irb suspension. + Debugger core for ChibiRuby. Protocol-agnostic primitives for breakpoints, stepping, and binding.break suspension. diff --git a/src/ChibiRuby.Debugger/MRubyDebugger.cs b/src/ChibiRuby.Debugger/MRubyDebugger.cs index dff5a291..471f3ccf 100644 --- a/src/ChibiRuby.Debugger/MRubyDebugger.cs +++ b/src/ChibiRuby.Debugger/MRubyDebugger.cs @@ -51,7 +51,7 @@ public sealed class MRubyDebugger(MRubyState mrb, MRubyCompiler compiler) : IMRu StepRequest? stepRequest; // Once any client has attached: subsequent stops with no client attached become no-op - // (so binding.irb doesn't hang the host thread after Rider/VSCode disconnects). + // (so binding.break doesn't hang the host thread after Rider/VSCode disconnects). bool hadAttachedClient; int disposed; @@ -204,9 +204,9 @@ static bool PathsLooselyEqual(string a, string b) return sep == '/' || sep == '\\'; } - void IMRubyDebuggerHook.OnBindingIrb(MRubyState state, RBinding binding) + void IMRubyDebuggerHook.OnBindingBreak(MRubyState state, RBinding binding) { - SuspendInPump(StopReason.BindingIrb, binding); + SuspendInPump(StopReason.BindingBreak, binding); } void IMRubyDebuggerHook.OnInstruction(MRubyState state, Irep irep, int pc) diff --git a/src/ChibiRuby.Debugger/StopReason.cs b/src/ChibiRuby.Debugger/StopReason.cs index e28d3cea..473a0f9d 100644 --- a/src/ChibiRuby.Debugger/StopReason.cs +++ b/src/ChibiRuby.Debugger/StopReason.cs @@ -2,7 +2,7 @@ namespace ChibiRuby.Debugger; public enum StopReason { - BindingIrb, + BindingBreak, LineBreakpoint, Step, } diff --git a/src/ChibiRuby/IMRubyDebuggerHook.cs b/src/ChibiRuby/IMRubyDebuggerHook.cs index f640cfdd..b8b3d403 100644 --- a/src/ChibiRuby/IMRubyDebuggerHook.cs +++ b/src/ChibiRuby/IMRubyDebuggerHook.cs @@ -3,8 +3,8 @@ namespace ChibiRuby; /// Optional VM-side hook installed via . Invoked on the VM thread. public interface IMRubyDebuggerHook { - /// Called when user code invokes binding.irb; expected to suspend the VM thread until resumed. - void OnBindingIrb(MRubyState state, RBinding binding); + /// Called when user code invokes binding.break (or its aliases binding.b / debugger); expected to suspend the VM thread until resumed. + void OnBindingBreak(MRubyState state, RBinding binding); /// Fires on every instruction while the hook is installed; implementations must early-out fast. void OnInstruction(MRubyState state, Irep irep, int pc); diff --git a/src/ChibiRuby/MRubyState.Binding.cs b/src/ChibiRuby/MRubyState.Binding.cs index 6eab3cbb..96c84b16 100644 --- a/src/ChibiRuby/MRubyState.Binding.cs +++ b/src/ChibiRuby/MRubyState.Binding.cs @@ -20,7 +20,7 @@ partial class MRubyState { public RClass BindingClass { get; private set; } = default!; - /// Optional hook for a debugger to suspend execution at binding.irb. + /// Optional hook for a debugger to suspend execution at binding.break. public IMRubyDebuggerHook? DebuggerHook { get; set; } /// Capture the caller's frame as a (for C#-defined Ruby methods). diff --git a/src/ChibiRuby/MRubyState.Init.cs b/src/ChibiRuby/MRubyState.Init.cs index 0b62796f..83561f7a 100644 --- a/src/ChibiRuby/MRubyState.Init.cs +++ b/src/ChibiRuby/MRubyState.Init.cs @@ -398,9 +398,11 @@ void InitBinding() DefineMethod(BindingClass, Intern("local_variable_get"u8), BindingMembers.LocalVariableGet); DefineMethod(BindingClass, Intern("local_variable_set"u8), BindingMembers.LocalVariableSet); DefineMethod(BindingClass, Intern("local_variable_defined?"u8), BindingMembers.LocalVariableDefined); - DefineMethod(BindingClass, Intern("irb"u8), BindingMembers.Irb); + DefineMethod(BindingClass, Intern("break"u8), BindingMembers.Break); + DefineMethod(BindingClass, Intern("b"u8), BindingMembers.Break); DefineMethod(KernelModule, Intern("binding"u8), BindingMembers.Binding); + DefineMethod(KernelModule, Intern("debugger"u8), BindingMembers.Debugger); } void InitException() diff --git a/src/ChibiRuby/StdLib/BindingMembers.cs b/src/ChibiRuby/StdLib/BindingMembers.cs index e3c789ca..ca241810 100644 --- a/src/ChibiRuby/StdLib/BindingMembers.cs +++ b/src/ChibiRuby/StdLib/BindingMembers.cs @@ -58,20 +58,27 @@ static class BindingMembers return binding.TryGetLocal(name, out _) ? MRubyValue.True : MRubyValue.False; }); - // Binding#irb - default implementation when no debugger is attached. - // The ChibiRuby.Debugger package replaces this with a hook-triggering implementation - // by calling DefineMethod again at attach time. - public static MRubyMethod Irb = new((state, self) => + // Binding#break (alias Binding#b) - ruby/debug compatible breakpoint. + public static MRubyMethod Break = new((state, self) => { var binding = AsBinding(state, self); + return TriggerBreak(state, binding); + }); + + // Kernel#debugger - ruby/debug compatible; equivalent to binding.break on the caller's frame. + public static MRubyMethod Debugger = new((state, self) => + TriggerBreak(state, state.CreateBinding())); + + static MRubyValue TriggerBreak(MRubyState state, RBinding binding) + { if (state.DebuggerHook is { } hook) { - hook.OnBindingIrb(state, binding); + hook.OnBindingBreak(state, binding); return MRubyValue.Nil; } - state.Raise(Names.RuntimeError, "binding.irb called but no debugger is attached (ChibiRuby.Debugger not initialized)"u8); + state.Raise(Names.RuntimeError, "binding.break called but no debugger is attached (ChibiRuby.Debugger not initialized)"u8); return MRubyValue.Nil; - }); + } static RBinding AsBinding(MRubyState state, MRubyValue self) { diff --git a/tests/ChibiRuby.Debugger.Dap.Tests/ChibiRubyDapServerTest.cs b/tests/ChibiRuby.Debugger.Dap.Tests/ChibiRubyDapServerTest.cs index 3da5b7ae..a973a0e3 100644 --- a/tests/ChibiRuby.Debugger.Dap.Tests/ChibiRubyDapServerTest.cs +++ b/tests/ChibiRuby.Debugger.Dap.Tests/ChibiRubyDapServerTest.cs @@ -36,9 +36,9 @@ public async Task Initialize_RespondsWithCapabilitiesAndInitializedEvent() } [Test] - public async Task Launch_RunsScript_StopsAtBindingIrb_AndContinues() + public async Task Launch_RunsScript_StopsAtBindingBreak_AndContinues() { - File.WriteAllText(scriptPath, "binding.irb\n"); + File.WriteAllText(scriptPath, "binding.break\n"); using var harness = new TestHarness(); await harness.InitializeAsync(); @@ -61,7 +61,7 @@ public async Task Launch_RunsScript_StopsAtBindingIrb_AndContinues() [Test] public async Task Evaluate_RunsRubyExpressionInBinding() { - File.WriteAllText(scriptPath, "x = 7\nbinding.irb\n"); + File.WriteAllText(scriptPath, "x = 7\nbinding.break\n"); using var harness = new TestHarness(); await harness.InitializeAsync(); @@ -80,7 +80,7 @@ public async Task Evaluate_RunsRubyExpressionInBinding() [Test] public async Task Variables_ListsLocalsAndSelf() { - File.WriteAllText(scriptPath, "a = 10\nb = 'hi'\nbinding.irb\n"); + File.WriteAllText(scriptPath, "a = 10\nb = 'hi'\nbinding.break\n"); using var harness = new TestHarness(); await harness.InitializeAsync(); @@ -109,7 +109,7 @@ public async Task Variables_ListsLocalsAndSelf() [Test] public async Task Evaluate_RubyRaiseReturnsErrorResponse_AndContinueStillWorks() { - File.WriteAllText(scriptPath, "binding.irb\n:ok\n"); + File.WriteAllText(scriptPath, "binding.break\n:ok\n"); using var harness = new TestHarness(); await harness.InitializeAsync(); diff --git a/tests/ChibiRuby.Debugger.Dap.Tests/StackTraceLineTest.cs b/tests/ChibiRuby.Debugger.Dap.Tests/StackTraceLineTest.cs index b9551e9b..5b6b6905 100644 --- a/tests/ChibiRuby.Debugger.Dap.Tests/StackTraceLineTest.cs +++ b/tests/ChibiRuby.Debugger.Dap.Tests/StackTraceLineTest.cs @@ -9,7 +9,7 @@ namespace ChibiRuby.Debugger.Dap.Tests; /// /// Verifies that Phase 2.1 DBG-section plumbing actually reaches the DAP wire: the -/// stackTrace response should carry the real source filename + line where binding.irb +/// stackTrace response should carry the real source filename + line where binding.break /// was invoked, not the (toplevel) / line 1 placeholder. /// [TestFixture] @@ -25,7 +25,7 @@ public async Task StackTrace_ReturnsRealLineFromDbgSection_Attach() var port = server.LocalEndpoint.Port; var scriptPath = Path.Combine(Path.GetTempPath(), $"dbg-{System.Guid.NewGuid():N}.rb"); - File.WriteAllText(scriptPath, "x = 10\ny = 20\nbinding.irb\nx + y\n"); + File.WriteAllText(scriptPath, "x = 10\ny = 20\nbinding.break\nx + y\n"); try { var vmDone = new ManualResetEventSlim(); @@ -58,7 +58,7 @@ public async Task StackTrace_ReturnsRealLineFromDbgSection_Attach() var frame0 = frames[0]; Assert.That(frame0.Line, Is.EqualTo((ulong)3), - "stackTrace should report the source line of binding.irb (line 3)"); + "stackTrace should report the source line of binding.break (line 3)"); var sourcePath = frame0.Source?.Path; Assert.That(sourcePath, Is.EqualTo(scriptPath)); diff --git a/tests/ChibiRuby.Debugger.Dap.Tests/TcpDapServerTest.cs b/tests/ChibiRuby.Debugger.Dap.Tests/TcpDapServerTest.cs index c9880111..92c5bc15 100644 --- a/tests/ChibiRuby.Debugger.Dap.Tests/TcpDapServerTest.cs +++ b/tests/ChibiRuby.Debugger.Dap.Tests/TcpDapServerTest.cs @@ -13,14 +13,14 @@ namespace ChibiRuby.Debugger.Dap.Tests; /// /// End-to-end test of the embedded host scenario: a host C# thread runs Ruby (with -/// binding.irb inside), listens for an attach over +/// binding.break inside), listens for an attach over /// loopback TCP, the test plays the client role. /// [TestFixture] public class TcpDapServerTest { [Test] - public async Task EmbeddedHost_BlocksAtBindingIrb_UntilClientAttaches_ThenContinues() + public async Task EmbeddedHost_BlocksAtBindingBreak_UntilClientAttaches_ThenContinues() { var state = MRubyState.Create(); var compiler = MRubyCompiler.Create(state); @@ -36,7 +36,7 @@ public async Task EmbeddedHost_BlocksAtBindingIrb_UntilClientAttaches_ThenContin { compiler.LoadSourceCode(""" secret = 42 - binding.irb + binding.break secret """u8); } @@ -46,7 +46,7 @@ public async Task EmbeddedHost_BlocksAtBindingIrb_UntilClientAttaches_ThenContin { IsBackground = true, Name = "test-vm" }; vmThread.Start(); - Assert.That(vmDone.Wait(200), Is.False, "VM should be blocked at binding.irb waiting for client"); + Assert.That(vmDone.Wait(200), Is.False, "VM should be blocked at binding.break waiting for client"); using var tcp = new TcpClient(); await tcp.ConnectAsync("127.0.0.1", port); diff --git a/tests/ChibiRuby.Debugger.Tests/MRubyDebuggerTest.cs b/tests/ChibiRuby.Debugger.Tests/MRubyDebuggerTest.cs index 40612a50..36bad44e 100644 --- a/tests/ChibiRuby.Debugger.Tests/MRubyDebuggerTest.cs +++ b/tests/ChibiRuby.Debugger.Tests/MRubyDebuggerTest.cs @@ -74,7 +74,7 @@ public void StopAndContinue_FromToplevel() })); var result = compiler.LoadSourceCode(""" - binding.irb + binding.break 42 """u8); @@ -95,7 +95,7 @@ public void Eval_CanCallMethodsOnReceiver() evalResult = dbg.Evaluate("self.class.to_s"); })); - compiler.LoadSourceCode("binding.irb"u8); + compiler.LoadSourceCode("binding.break"u8); Assert.That(evalResult, Is.Not.Null); Assert.That(evalResult!.IsError, Is.False); @@ -113,7 +113,7 @@ public void Eval_SyntaxErrorIsReportedNotPropagated() })); var result = compiler.LoadSourceCode(""" - binding.irb + binding.break :ok """u8); @@ -130,9 +130,9 @@ public void Eval_RubyRaiseIsReportedNotPropagated() evalResult = dbg.Evaluate("raise 'boom'"); })); - // The raise inside eval must not bubble out of binding.irb. + // The raise inside eval must not bubble out of binding.break. var result = compiler.LoadSourceCode(""" - binding.irb + binding.break :ok """u8); @@ -154,7 +154,7 @@ public void Eval_AfterRaise_NextEvalStillWorks() })); var result = compiler.LoadSourceCode(""" - binding.irb + binding.break 42 """u8); @@ -179,7 +179,7 @@ public void Eval_AccessLocalsViaBindingHandle() compiler.LoadSourceCode(""" x = 5 - binding.irb + binding.break """u8); Assert.That(evalResult, Is.Not.Null); @@ -200,7 +200,7 @@ public void Eval_BareLocalIdentifier_ResolvesToBindingLocal() compiler.LoadSourceCode(""" hero = "knight" - binding.irb + binding.break """u8); Assert.That(evalResult, Is.Not.Null); @@ -220,7 +220,7 @@ public void Eval_LocalIdentifierInExpression_Works() compiler.LoadSourceCode(""" hp = 100 bonus = 25 - binding.irb + binding.break """u8); Assert.That(evalResult, Is.Not.Null); @@ -232,7 +232,7 @@ public void Eval_LocalIdentifierInExpression_Works() public void Eval_BindingLocalVariableSet_WritesBackToOuterScope() { // Regression: typing `binding.local_variable_set(:x, 5)` in the debug REPL must - // mutate the *outer* (binding.irb) scope's local, not a throwaway eval-scope + // mutate the *outer* (binding.break) scope's local, not a throwaway eval-scope // binding. The wrapper shadows Kernel#binding with the captured outer binding. EvalResult? setResult = null; EvalResult? getResult = null; @@ -244,7 +244,7 @@ public void Eval_BindingLocalVariableSet_WritesBackToOuterScope() var result = compiler.LoadSourceCode(""" hp = 100 - binding.irb + binding.break hp """u8); @@ -270,7 +270,7 @@ public void Eval_LocalIdentifier_MultilineUserSourceStillCompiles() compiler.LoadSourceCode(""" name = "sword" - binding.irb + binding.break """u8); Assert.That(evalResult, Is.Not.Null); @@ -289,7 +289,7 @@ public void Eval_DoesNotLeakTemporaryGlobal() compiler.LoadSourceCode(""" x = 42 - binding.irb + binding.break """u8); Assert.That(firstEval!.IsError, Is.False); @@ -309,7 +309,7 @@ public void MultipleEvals_BeforeContinue() results.Add(dbg.Evaluate("3")); })); - compiler.LoadSourceCode("binding.irb"u8); + compiler.LoadSourceCode("binding.break"u8); Assert.That(results, Has.Count.EqualTo(3)); Assert.That(results[0].Value.IntegerValue, Is.EqualTo(1)); @@ -328,7 +328,7 @@ public void Eval_TypeErrorIsReportedNotPropagated() })); var result = compiler.LoadSourceCode(""" - binding.irb + binding.break :done """u8); @@ -346,11 +346,11 @@ public void Eval_Raise_WhenStoppedInsideMethod() evalResult = dbg.Evaluate("raise 'inside-method'"); })); - // binding.irb is called from inside a method body so the surrounding call stack - // depth is non-trivial (root + method + Send :binding + Send :irb). + // binding.break is called from inside a method body so the surrounding call stack + // depth is non-trivial (root + method + Send :binding + Send :break). var result = compiler.LoadSourceCode(""" def f(n) - binding.irb + binding.break n * 2 end f(21) @@ -377,7 +377,7 @@ def helper })); var result = compiler.LoadSourceCode(""" - binding.irb + binding.break 42 """u8); @@ -397,11 +397,11 @@ public void StopEvent_CarriesBinding() compiler.LoadSourceCode(""" zz = 99 - binding.irb + binding.break """u8); Assert.That(captured, Is.Not.Null); - Assert.That(captured!.Reason, Is.EqualTo(StopReason.BindingIrb)); + Assert.That(captured!.Reason, Is.EqualTo(StopReason.BindingBreak)); Assert.That(captured.Binding.TryGetLocal(mrb.Intern("zz"u8), out var v), Is.True); Assert.That(v.IntegerValue, Is.EqualTo(99)); } diff --git a/tests/ChibiRuby.Tests/BindingTest.cs b/tests/ChibiRuby.Tests/BindingTest.cs index 04b6b993..08a93a93 100644 --- a/tests/ChibiRuby.Tests/BindingTest.cs +++ b/tests/ChibiRuby.Tests/BindingTest.cs @@ -276,11 +276,11 @@ def make } [Test] - public void BindingIrb_RaisesWhenNoDebuggerAttached() + public void BindingBreak_RaisesWhenNoDebuggerAttached() { Assert.That(mrb.DebuggerHook, Is.Null); Assert.Throws(() => - compiler.LoadSourceCode("binding.irb"u8)); + compiler.LoadSourceCode("binding.break"u8)); } sealed class CaptureHook : IMRubyDebuggerHook @@ -288,7 +288,7 @@ sealed class CaptureHook : IMRubyDebuggerHook public RBinding? CapturedBinding; public int CallCount; - public void OnBindingIrb(MRubyState state, RBinding binding) + public void OnBindingBreak(MRubyState state, RBinding binding) { CapturedBinding = binding; CallCount++; @@ -297,19 +297,19 @@ public void OnBindingIrb(MRubyState state, RBinding binding) public void OnInstruction(MRubyState state, Irep irep, int pc) { - // No-op for these tests; the binding.irb path is what we exercise. + // No-op for these tests; the binding.break path is what we exercise. } } [Test] - public void BindingIrb_InvokesDebuggerHook_AndContinues() + public void BindingBreak_InvokesDebuggerHook_AndContinues() { var hook = new CaptureHook(); mrb.DebuggerHook = hook; var result = compiler.LoadSourceCode(""" x = 100 - binding.irb + binding.break x + 1 """u8); @@ -320,6 +320,26 @@ public void BindingIrb_InvokesDebuggerHook_AndContinues() Assert.That(result.IntegerValue, Is.EqualTo(101)); } + [Test] + public void BindingB_AndKernelDebugger_AreAliasesOfBindingBreak() + { + var hook = new CaptureHook(); + mrb.DebuggerHook = hook; + + var result = compiler.LoadSourceCode(""" + x = 5 + binding.b + debugger + x + 1 + """u8); + + Assert.That(hook.CallCount, Is.EqualTo(2)); + Assert.That(hook.CapturedBinding, Is.Not.Null); + Assert.That(hook.CapturedBinding!.TryGetLocal(mrb.Intern("x"u8), out var xValue), Is.True); + Assert.That(xValue.IntegerValue, Is.EqualTo(5)); + Assert.That(result.IntegerValue, Is.EqualTo(6)); + } + [Test] public void Binding_CapturesLocalsInMethodBody() { @@ -331,7 +351,7 @@ public void Binding_CapturesLocalsInMethodBody() def f aaa = 7 bbb = "hello" - binding.irb + binding.break end f """u8);