@@ -73,6 +73,8 @@ pub const Lua = struct {
7373 Runtime ,
7474 /// Breakpoint could not be set at the specified line.
7575 InvalidBreakpoint ,
76+ /// Debug break occurred during execution.
77+ Break ,
7678 };
7779
7880 /// Assert handler function type for Luau VM assertions.
@@ -213,10 +215,13 @@ pub const Lua = struct {
213215 /// - `panic(state: *State, errcode: i32) void` - Called on unprotected errors (if longjmp is used)
214216 /// - `userthread(parent: ?*State, thread: *State) void` - Called when thread is created/destroyed
215217 /// - `useratom(s: []const u8) i16` - Called when string is created; returns atom ID
216- /// - `debugbreak(state: *State, ar: Debug) void` - Called on BREAK instruction
217- /// - `debugstep(state: *State, ar: Debug) void` - Called after each instruction in single step
218- /// - `debuginterrupt(state: *State, ar: Debug) void` - Called on thread execution interrupt
219- /// - `debugprotectederror(state: *State) void` - Called when protected call results in error
218+ /// - `debugbreak(lua: *Lua, ar: Debug) void` - Called when breakpoint is hit. Note that breakpoints
219+ /// set with `breakpoint(line)` in Lua code only trigger this callback - they don't automatically
220+ /// interrupt execution. Call `lua.debugBreak()` within this callback to actually interrupt
221+ /// execution and return `error.Break` to the caller.
222+ /// - `debugstep(lua: *Lua, ar: Debug) void` - Called after each instruction in single step
223+ /// - `debuginterrupt(lua: *Lua, ar: Debug) void` - Called on thread execution interrupt
224+ /// - `debugprotectederror(lua: *Lua) void` - Called when protected call results in error
220225 /// - `onallocate(state: *State, osize: usize, nsize: usize) void` - Called when a memory operation occurs
221226 /// (allocation when osize=0, deallocation when nsize=0, reallocation otherwise).
222227 /// Note: This callback is only triggered for Luau's internal allocations, not for all memory operations
@@ -348,15 +353,15 @@ pub const Lua = struct {
348353 if (@hasDecl (CallbackType , "debugbreak" )) {
349354 cb .debugbreak = struct {
350355 fn wrapper (L : ? State.LuaState , ar : ? * State.Debug ) callconv (.C ) void {
351- var state = State { . lua = L .? } ;
356+ var lua = Lua . fromState ( L .? ) ;
352357 const debug_info = debug .Debug .fromC (ar .? );
353358
354359 if (comptime is_instance ) {
355- const callbacks_struct = state .callbacks ();
360+ const callbacks_struct = lua . state .callbacks ();
356361 const instance : * CallbackType = @ptrCast (@alignCast (callbacks_struct .userdata .? ));
357- instance .debugbreak (& state , debug_info );
362+ instance .debugbreak (& lua , debug_info );
358363 } else {
359- CallbackType .debugbreak (& state , debug_info );
364+ CallbackType .debugbreak (& lua , debug_info );
360365 }
361366 }
362367 }.wrapper ;
@@ -365,15 +370,15 @@ pub const Lua = struct {
365370 if (@hasDecl (CallbackType , "debugstep" )) {
366371 cb .debugstep = struct {
367372 fn wrapper (L : ? State.LuaState , ar : ? * State.Debug ) callconv (.C ) void {
368- var state = State { . lua = L .? } ;
373+ var lua = Lua . fromState ( L .? ) ;
369374 const debug_info = debug .Debug .fromC (ar .? );
370375
371376 if (comptime is_instance ) {
372- const callbacks_struct = state .callbacks ();
377+ const callbacks_struct = lua . state .callbacks ();
373378 const instance : * CallbackType = @ptrCast (@alignCast (callbacks_struct .userdata .? ));
374- instance .debugstep (& state , debug_info );
379+ instance .debugstep (& lua , debug_info );
375380 } else {
376- CallbackType .debugstep (& state , debug_info );
381+ CallbackType .debugstep (& lua , debug_info );
377382 }
378383 }
379384 }.wrapper ;
@@ -382,15 +387,15 @@ pub const Lua = struct {
382387 if (@hasDecl (CallbackType , "debuginterrupt" )) {
383388 cb .debuginterrupt = struct {
384389 fn wrapper (L : ? State.LuaState , ar : ? * State.Debug ) callconv (.C ) void {
385- var state = State { . lua = L .? } ;
390+ var lua = Lua . fromState ( L .? ) ;
386391 const debug_info = debug .Debug .fromC (ar .? );
387392
388393 if (comptime is_instance ) {
389- const callbacks_struct = state .callbacks ();
394+ const callbacks_struct = lua . state .callbacks ();
390395 const instance : * CallbackType = @ptrCast (@alignCast (callbacks_struct .userdata .? ));
391- instance .debuginterrupt (& state , debug_info );
396+ instance .debuginterrupt (& lua , debug_info );
392397 } else {
393- CallbackType .debuginterrupt (& state , debug_info );
398+ CallbackType .debuginterrupt (& lua , debug_info );
394399 }
395400 }
396401 }.wrapper ;
@@ -399,14 +404,14 @@ pub const Lua = struct {
399404 if (@hasDecl (CallbackType , "debugprotectederror" )) {
400405 cb .debugprotectederror = struct {
401406 fn wrapper (L : ? State.LuaState ) callconv (.C ) void {
402- var state = State { . lua = L .? } ;
407+ var lua = Lua . fromState ( L .? ) ;
403408
404409 if (comptime is_instance ) {
405- const callbacks_struct = state .callbacks ();
410+ const callbacks_struct = lua . state .callbacks ();
406411 const instance : * CallbackType = @ptrCast (@alignCast (callbacks_struct .userdata .? ));
407- instance .debugprotectederror (& state );
412+ instance .debugprotectederror (& lua );
408413 } else {
409- CallbackType .debugprotectederror (& state );
414+ CallbackType .debugprotectederror (& lua );
410415 }
411416 }
412417 }.wrapper ;
@@ -415,8 +420,8 @@ pub const Lua = struct {
415420 if (@hasDecl (CallbackType , "onallocate" )) {
416421 cb .onallocate = struct {
417422 fn wrapper (L : ? State.LuaState , osize : usize , nsize : usize ) callconv (.C ) void {
418- if (L ) | lua | {
419- var state = State { .lua = lua };
423+ if (L ) | lua_state | {
424+ var state = State { .lua = lua_state };
420425
421426 if (comptime is_instance ) {
422427 const callbacks_struct = state .callbacks ();
@@ -471,6 +476,50 @@ pub const Lua = struct {
471476 self .state .singleStep (enabled );
472477 }
473478
479+ /// Interrupt thread execution during debug callbacks.
480+ ///
481+ /// This method should be called from within debug callbacks (debugbreak, debugstep, debuginterrupt)
482+ /// to interrupt the current execution and return control to the caller. When called, it sets the
483+ /// VM's status to LUA_BREAK, causing the current function to return with `error.Break`.
484+ ///
485+ /// Note: Breakpoints set with `breakpoint(line)` in Lua code only trigger debug callbacks - they
486+ /// don't automatically interrupt execution. You must call `debugBreak()` within your callback
487+ /// to actually interrupt and return control to your application.
488+ ///
489+ /// Flow:
490+ /// 1. Breakpoint hits → VM calls your debugbreak callback
491+ /// 2. In callback → call `debugBreak()` to interrupt execution
492+ /// 3. VM sets status → L.status = LUA_BREAK
493+ /// 4. Function returns → `error.Break` to caller
494+ /// 5. Can resume → call function again to continue from where it left off
495+ ///
496+ /// Example:
497+ /// ```zig
498+ /// const DebugCallbacks = struct {
499+ /// pub fn debugbreak(self: *@This(), lua: *Lua, ar: Debug) void {
500+ /// std.log.info("Hit breakpoint at line {}", .{ar.current_line});
501+ /// // This actually interrupts execution and returns error.Break
502+ /// lua.debugBreak();
503+ /// }
504+ /// };
505+ ///
506+ /// var callbacks = DebugCallbacks{};
507+ /// lua.setCallbacks(&callbacks);
508+ ///
509+ /// // When breakpoint hits, function returns error.Break instead of continuing
510+ /// const result = func.call(.{}, i32) catch |err| switch (err) {
511+ /// error.Break => {
512+ /// // Execution was interrupted, can examine state or resume later
513+ /// return func.call(.{}, i32); // Resume execution
514+ /// },
515+ /// else => return err,
516+ /// };
517+ /// ```
518+ ///
519+ pub fn debugBreak (self : Self ) void {
520+ self .state .break_ ();
521+ }
522+
474523 /// A reference to a Lua value.
475524 ///
476525 /// Holds a reference ID that can be used to retrieve the value later.
@@ -833,6 +882,7 @@ pub const Lua = struct {
833882 const result = self .ref .lua .call (args , R , false );
834883 return switch (result .status ) {
835884 .ok , .yield = > result .result .? ,
885+ .break_debug = > error .Break ,
836886 .errmem = > error .OutOfMemory ,
837887 else = > error .Runtime ,
838888 };
@@ -1662,6 +1712,7 @@ pub const Lua = struct {
16621712
16631713 return switch (result .status ) {
16641714 .ok , .yield = > result .result .? ,
1715+ .break_debug = > error .Break ,
16651716 .errmem = > error .OutOfMemory ,
16661717 else = > error .Runtime ,
16671718 };
0 commit comments