@@ -1387,7 +1387,7 @@ pub const Lua = opaque {
13871387 ok = c .LUA_OK ,
13881388
13891389 /// Coroutine has suspended execution via yield. Indicates normal coroutine suspension, not an
1390- /// error condition. The coroutine can be resumed later with lua_resume() .
1390+ /// error condition. The coroutine can be resumed later with `resumeCoroutine()` .
13911391 yield = c .LUA_YIELD ,
13921392
13931393 /// Indicates that the last execution results in a Lua runtime error. This may indicate usage of
@@ -1905,6 +1905,91 @@ pub const Lua = opaque {
19051905 return c .lua_yield (asState (lua ), nresults );
19061906 }
19071907
1908+ /// Dumps the function on the top of the stack to a binary chunk. This function can be restored to the stack by
1909+ /// calling `load()` with the binary chunk written by this function. The restored function is equivalent to the one
1910+ /// dumped.
1911+ ///
1912+ /// As it produces parts of the chunk, `dump()` calls functions on the given writer instance.
1913+ ///
1914+ /// This function does not pop the Lua function from the stack.
1915+ ///
1916+ /// From: `int lua_dump(lua_State *L, lua_Writer writer, void *data);`
1917+ /// Refer to: https://www.lua.org/manual/5.1/manual.html#lua_dump
1918+ /// Stack Behavior: `[-0, +0, m]`
1919+ pub fn dump (lua : * Lua , writer : std.io.AnyWriter ) anyerror ! void {
1920+ const DumpContext = struct {
1921+ writer : std.io.AnyWriter ,
1922+
1923+ fn dumpAdapter (l : * Lua , bytes : ? * const anyopaque , size : usize , ud : ? * anyopaque ) callconv (.c ) i32 {
1924+ assert (bytes != null );
1925+ assert (ud != null );
1926+
1927+ _ = l ;
1928+
1929+ const context : * @This () = @alignCast (@ptrCast (ud ));
1930+ const slice : []const u8 = @as ([* ]const u8 , @ptrCast (bytes ))[0.. size ];
1931+ context .writer .writeAll (slice ) catch | err | {
1932+ return @intCast (@intFromError (err ));
1933+ };
1934+ return 0 ;
1935+ }
1936+ };
1937+
1938+ var context : DumpContext = .{
1939+ .writer = writer ,
1940+ };
1941+
1942+ const res = c .lua_dump (asState (lua ), @ptrCast (& DumpContext .dumpAdapter ), & context );
1943+
1944+ return switch (res ) {
1945+ 0 = > return ,
1946+ else = > | err | {
1947+ const error_value : std .meta .Int (.unsigned , @bitSizeOf (anyerror )) = @intCast (err );
1948+ return @errorFromInt (error_value );
1949+ },
1950+ };
1951+ }
1952+
1953+ /// Loads the function in the given chunk and pushes the valid function to the top of the stack. If there is an
1954+ /// error with the syntax of the function, or the data cannot be loaded, then an error is returned instead.
1955+ ///
1956+ /// This function only loads a chunk; it does not run it. Automatically detects whether the chunk is text or binary.
1957+ ///
1958+ /// From: `int lua_load(lua_State *L, lua_Reader reader, void *data, const char *chunkname);`
1959+ /// Refer to: https://www.lua.org/manual/5.1/manual.html#lua_load
1960+ /// Stack Behavior: `[-0, +1, -]`
1961+ pub fn load (lua : * Lua , reader : std.io.AnyReader , chunkname : ? [:0 ]const u8 ) Lua.Status {
1962+ const LoadContext = struct {
1963+ reader : std.io.AnyReader ,
1964+ read_buffer : []u8 ,
1965+
1966+ fn loadAdapter (l : * Lua , ud : ? * anyopaque , size : * usize ) callconv (.c ) [* ]const u8 {
1967+ assert (ud != null );
1968+
1969+ const context : * @This () = @alignCast (@ptrCast (ud .? ));
1970+ const actual = context .reader .read (context .read_buffer ) catch | err | {
1971+ _ = l .pushFString ("Unable to load function, found error '%s' while reading." , .{@errorName (err ).ptr });
1972+ l .raiseError ();
1973+ };
1974+ size .* = actual ;
1975+ return @ptrCast (context .read_buffer .ptr );
1976+ }
1977+ };
1978+
1979+ // TODO: The read buffer size should comptime constant and user controlled? Do users ever create functions that
1980+ // are more than a few kilobytes?
1981+ // TODO: Should the default be larger?
1982+ var read_buffer : [1024 ]u8 = undefined ;
1983+ var context : LoadContext = .{
1984+ .reader = reader ,
1985+ .read_buffer = read_buffer [0.. ],
1986+ };
1987+
1988+ const s = c .lua_load (asState (lua ), @ptrCast (& LoadContext .loadAdapter ), & context , if (chunkname ) | p | p .ptr else null );
1989+ assert (Lua .Status .is_status (s ));
1990+ return @enumFromInt (s );
1991+ }
1992+
19081993 /// Used by C functions to validate received arguments.
19091994 /// Checks whether the function argument `narg` is a number and returns this number cast to a `lua_Integer`.
19101995 ///
@@ -3745,3 +3830,49 @@ test "ref and unref in registry" {
37453830 lua .pushValue (Lua .PseudoIndex .Registry );
37463831 try std .testing .expectEqual (length_before , lua .lengthOf (1 ));
37473832}
3833+
3834+ test "Lua functions can be serialized and restored using dump() and load()" {
3835+ const lua = try Lua .init (std .testing .allocator );
3836+ defer lua .deinit ();
3837+
3838+ var buf : [256 ]u8 = undefined ;
3839+ var fbs_write = std .io .fixedBufferStream (& buf );
3840+
3841+ try lua .doString ("return function(x) return x * 2 end" );
3842+ try std .testing .expectEqual (1 , lua .getTop ()); // The stack should contain one value, a function.
3843+ try lua .dump (fbs_write .writer ().any ());
3844+
3845+ lua .pop (1 );
3846+ try std .testing .expectEqual (0 , lua .getTop ()); // The stack should be empty, ensuring that the function is fully restored from the binary chunk.
3847+
3848+ var fbs_read = std .io .fixedBufferStream (fbs_write .getWritten ());
3849+ const status = lua .load (fbs_read .reader ().any (), null );
3850+ try std .testing .expectEqual (Lua .Status .ok , status ); // The function should be loaded to the stack successfully.
3851+
3852+ lua .pushInteger (21 );
3853+ try lua .protectedCall (1 , 1 , 0 );
3854+ try std .testing .expectEqual (42 , try lua .toIntegerStrict (-1 )); // The function should be the "multiply by two" function from above.
3855+ }
3856+
3857+ test "dump() should report the same errors returned by the AnyWriter" {
3858+ const lua = try Lua .init (std .testing .allocator );
3859+ defer lua .deinit ();
3860+
3861+ var buf : [16 ]u8 = undefined ;
3862+ var fbs = std .io .fixedBufferStream (& buf );
3863+
3864+ try lua .doString ("return function(x) return x * 2 end" );
3865+ const actual = lua .dump (fbs .writer ().any ());
3866+ try std .testing .expectError (error .NoSpaceLeft , actual );
3867+ }
3868+
3869+ test "load() should report syntax errors when loading invalid binary chunk" {
3870+ const lua = try Lua .init (std .testing .allocator );
3871+ defer lua .deinit ();
3872+
3873+ var buf : [3 ]u8 = .{ 0 , 0 , 0 };
3874+ var fbs = std .io .fixedBufferStream (& buf );
3875+
3876+ const actual = lua .load (fbs .reader ().any (), null );
3877+ try std .testing .expectEqual (Lua .Status .syntax_error , actual );
3878+ }
0 commit comments