@@ -2628,14 +2628,22 @@ pub const Lua = opaque {
26282628 /// Represents different types of events that can be triggered during Lua execution. These events are received by
26292629 /// hooks to inspect or modify the execution of code in the Lua instance.
26302630 pub const HookEventKind = enum (i32 ) {
2631- /// Function call event
2631+ /// The hook is called when the interpreter calls a function.
2632+ /// The hook is called just after Lua enters the new function, before the function gets its arguments.
26322633 call = c .LUA_HOOKCALL ,
2633- /// Normal function return event
2634+
2635+ /// The hook is called when the interpreter returns from a function. The hook is called just before Lua leaves
2636+ /// the function. You have no access to the values to be returned by the function.
26342637 ret = c .LUA_HOOKRET ,
2635- /// Line execution event
2638+
2639+ /// The hook is called when the interpreter is about to start the execution of a new line of code, or when it
2640+ /// jumps back in the code (even to the same line). This event only happens while Lua is executing a Lua function.
26362641 line = c .LUA_HOOKLINE ,
2637- /// Instruction count event
2642+
2643+ /// The hook is called after the interpreter executes every count instructions. This event only happens while
2644+ /// Lua is executing a Lua function.
26382645 count = c .LUA_HOOKCOUNT ,
2646+
26392647 /// Tail call return event - occurs when Lua is simulating a return from a function that performed a tail call
26402648 tailret = c .LUA_HOOKTAILRET ,
26412649
@@ -2660,6 +2668,9 @@ pub const Lua = opaque {
26602668 ///
26612669 /// Note: While Lua is running a hook, it disables other calls to hooks. If a hook calls back
26622670 /// Lua to execute a function or a chunk, this execution occurs without any calls to hooks.
2671+ ///
2672+ /// Note: throughout the Lua documentation, this debug info is often referred to as an "activation record" and passed as
2673+ /// a parameter named `ar`.
26632674 pub const DebugInfo = extern struct {
26642675 /// The event that triggered the hook. When hooks are called, this field indicates
26652676 /// the specific event type that triggered it
@@ -2902,6 +2913,80 @@ pub const Lua = opaque {
29022913 @memcpy (simplifiedInterface .short_src [0.. DebugShortSourceLen ], info .short_src [0.. DebugShortSourceLen ]);
29032914 return simplifiedInterface ;
29042915 }
2916+
2917+ /// Hooks allow for Zig code to be invoked as part of the lifecycle of executing Lua code. Hooks may be registered
2918+ /// with the Lua runtime and will be invoked at requested points in time, such as when entering a function.
2919+ ///
2920+ /// Whenever a hook is called by the runtime, the `info.event` argument is set to the specific set to the specific
2921+ /// `HookEventKind` that triggered the hook. For certain kinds of hooks, additional information will be set in the
2922+ /// debug info struct:
2923+ ///
2924+ /// * For `line` events: the field `currentline` is also set. For other information the hook function should
2925+ /// call `getInfo()`.
2926+ /// * For return events the `info.event` may either be `ret`, indicating a normal function return, or `tailret` which
2927+ /// indicates that Lua is simulating a return from a function that did a tail call. For `tailret` events it is
2928+ /// useless to call `getInfo()`.
2929+ ///
2930+ /// While Lua is running a hook, it disables other calls to hooks. Therefore, if a hook calls back Lua to execute a
2931+ /// function or a chunk, this execution occurs **without** any calls to hooks.
2932+ ///
2933+ /// From: `typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);`
2934+ /// Refer to: https://www.lua.org/manual/5.1/manual.html#lua_Hook
2935+ pub const HookFunction = * const fn (lua : * Lua , info : * Lua.DebugInfo ) callconv (.c ) void ;
2936+
2937+ /// A flag enumeration ("mask" or "bitmask") used to register a callback function ("hook") on certain points in the
2938+ /// execution lifecycle.
2939+ pub const HookMask = packed struct (u32 ) {
2940+ /// Used as the `mask` parameter to `setHook()` in order to disable a hook.
2941+ pub const disabled : HookMask = .{};
2942+
2943+ /// Indicates that the hook function should be invoked when the Lua runtime calls a function.
2944+ on_call : bool = false ,
2945+
2946+ /// Indicates that the hook function should be invoked when the Lua runtime returns from a function (or performs
2947+ /// tail call recursion).
2948+ on_return : bool = false ,
2949+
2950+ /// Indicates that the hook function should be invoked when the Lua runtime moves to execute the next line of
2951+ /// a function.
2952+ on_line : bool = false ,
2953+
2954+ /// Indicates that the hook function should be invoked after executing `count` instructions.
2955+ on_count : bool = false ,
2956+
2957+ /// Unused and ignored.
2958+ __padding : u28 = undefined ,
2959+ };
2960+
2961+ /// Sets the debugging hook function. Argument `f` is the hook function. `mask` specifies on which events the hook
2962+ /// will be called.
2963+ ///
2964+ /// Events include:
2965+ /// - Call hook: Called when the interpreter calls a function, just after Lua enters the new function
2966+ /// - Return hook: Called when the interpreter returns from a function, just before Lua leaves the function
2967+ /// - Line hook: Called when starting execution of a new line of code or jumping back in code
2968+ /// - Count hook: Called after executing every `count` instructions
2969+ ///
2970+ /// Example:
2971+ /// ```zig
2972+ /// const lua: *Lua = ...;
2973+ /// const Example = struct {
2974+ /// fn exHook(l: *Lua, info: *Lua.DebugInfo) callconv(.c) void {
2975+ /// ...
2976+ /// }
2977+ /// };
2978+ /// lua.setHook(Example.exHook, .{ .on_call = true, .on_return = true }, 0);
2979+ /// ```
2980+ ///
2981+ /// A hook is disabled by setting `mask` to `HookMask.disabled` (the empty mask `.{}`).
2982+ ///
2983+ /// From: `int lua_sethook(lua_State *L, lua_Hook f, int mask, int count);`
2984+ /// Refer to: https://www.lua.org/manual/5.1/manual.html#lua_sethook
2985+ /// Stack Behavior: `[-0, +0, -]`
2986+ pub fn setHook (lua : * Lua , f : HookFunction , mask : HookMask , count : i32 ) void {
2987+ const res = c .lua_sethook (asState (lua ), @ptrCast (f ), @bitCast (mask ), count );
2988+ assert (1 == res );
2989+ }
29052990};
29062991
29072992test "Lua can be initialized with an allocator" {
@@ -5536,3 +5621,40 @@ test "getStack() can be used to inspect the call stack" {
55365621 try std .testing .expect (lua .isString (2 ));
55375622 try std .testing .expectEqualStrings ("bar" , try lua .toLString (-1 ));
55385623}
5624+
5625+ test "setHook() can be used register a callback to intercept function calls" {
5626+ const lua = try Lua .init (std .testing .allocator );
5627+ defer lua .deinit ();
5628+
5629+ const T = struct {
5630+ fn hook (l : * Lua , info : * Lua.DebugInfo ) callconv (.c ) void {
5631+ std .testing .expectEqual (Lua .HookEventKind .call , info .event ) catch unreachable ;
5632+
5633+ if (! l .getInfo ("nSLufl" , info )) {
5634+ return l .raiseErrorFormat ("Could not get info." , .{});
5635+ }
5636+
5637+ std .testing .expect (info .name == null ) catch unreachable ;
5638+ std .testing .expectEqualStrings ("Lua\x00 " , info .what .? [0.. 4]) catch unreachable ;
5639+ std .testing .expectEqualStrings ("[string \" function bar()...\" ]\x00 " , info .short_src [0.. 29]) catch unreachable ;
5640+ std .testing .expectEqual (1 , info .currentline ) catch unreachable ;
5641+ std .testing .expectEqual (0 , info .nups ) catch unreachable ;
5642+ std .testing .expectEqual (1 , info .linedefined ) catch unreachable ;
5643+ std .testing .expectEqual (3 , info .lastlinedefined ) catch unreachable ;
5644+ }
5645+ };
5646+
5647+ const expected_source =
5648+ \\function bar()
5649+ \\ return 1
5650+ \\end
5651+ ;
5652+ try lua .doString (expected_source );
5653+
5654+ lua .setHook (T .hook , .{ .on_call = true }, 0 );
5655+ try std .testing .expectEqual (Lua .Type .function , lua .getGlobal ("bar" ));
5656+ try lua .callProtected (0 , Lua .MultipleReturn , 1 );
5657+ try std .testing .expectEqual (1 , lua .getTop ());
5658+ try std .testing .expect (lua .isInteger (-1 ));
5659+ try std .testing .expectEqual (1 , try lua .toIntegerStrict (-1 ));
5660+ }
0 commit comments