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). | [](https://www.nuget.org/packages/ChibiRuby.Compiler) |
| ChibiRuby.Cli | dotnet tool with subcommands (e.g. `compile`) for ChibiRuby workflows | [](https://www.nuget.org/packages/ChibiRuby.Cli) |
| ChibiRuby.Serializer | Converts between Ruby and C# objects | [](https://www.nuget.org/packages/ChibiRuby.Serializer) |
-| ChibiRuby.Debugger | Protocol-agnostic debugger core (breakpoints, stepping, `binding.irb` suspension) | [](https://www.nuget.org/packages/ChibiRuby.Debugger) |
+| ChibiRuby.Debugger | Protocol-agnostic debugger core (breakpoints, stepping, `binding.break` suspension) | [](https://www.nuget.org/packages/ChibiRuby.Debugger) |
| ChibiRuby.Debugger.Dap | DAP server (TCP) for any DAP-compatible editor — see [Debugger](#debugger) | [](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);