├── test ├── engine │ ├── generated │ │ ├── blank.luau │ │ ├── v2.luau │ │ ├── v4.luau │ │ ├── duplicate.lua │ │ ├── duplicate.luau │ │ ├── duplicate2.lua │ │ ├── duplicate2.luau │ │ ├── duplicate3.luau │ │ ├── duplicate2 │ │ │ └── init.luau │ │ ├── duplicate3 │ │ │ └── init.luau │ │ ├── v3.luau │ │ ├── v1.luau │ │ ├── toomany.luau │ │ ├── relative.luau │ │ ├── v5.luau │ │ ├── v6.luau │ │ ├── yieldtoomany.luau │ │ └── yielderror.luau │ ├── legacy │ │ ├── A.lua │ │ └── init.lua │ ├── module │ │ ├── init.luau │ │ ├── withinit │ │ │ ├── child.luau │ │ │ ├── init.luau │ │ │ └── descendant │ │ │ │ └── init.luau │ │ ├── withinit_bad │ │ │ ├── child.luau │ │ │ └── init.luau │ │ └── sub.luau │ ├── samples │ │ ├── Utils │ │ │ ├── utilB.luau │ │ │ └── utilA.luau │ │ ├── anyvalue.luau │ │ ├── cyclic.luau │ │ ├── cyclic2.luau │ │ └── simpleLib.luau │ ├── sublibrary │ │ ├── inner.luau │ │ ├── subdir │ │ │ ├── inner.luau │ │ │ ├── .luaurc │ │ │ └── foo.luau │ │ ├── kool.luau │ │ └── .luaurc │ ├── dev.luau │ └── .luaurc ├── cli │ ├── run.luau │ ├── bundle.luau │ └── test.luau ├── standard │ ├── require │ │ ├── module │ │ │ ├── bar.luau │ │ │ └── foo.luau │ │ └── init.luau │ ├── thread │ │ └── module.luau │ ├── ffi │ │ ├── zune.toml │ │ └── sample.zig │ ├── sqlite │ │ └── zune.toml │ ├── net │ │ ├── init.luau │ │ ├── http │ │ │ └── request.test.luau │ │ └── socket.test.luau │ ├── serde │ │ ├── base64.test.luau │ │ ├── brotli.test.luau │ │ ├── init.luau │ │ ├── zlib.test.luau │ │ ├── gzip.test.luau │ │ ├── yaml.test.luau │ │ ├── zstd.test.luau │ │ └── lz4.test.luau │ ├── crypto │ │ ├── uuid.test.luau │ │ ├── init.luau │ │ ├── password.test.luau │ │ ├── random.test.luau │ │ └── aead │ │ │ ├── isap.luau │ │ │ └── salsa_poly.luau │ ├── platform.test.luau │ ├── testing.test.luau │ └── random.test.luau ├── zune.test.luau └── runner.zig ├── src ├── core │ ├── utils │ │ ├── lib.zig │ │ ├── old_writer.zig │ │ ├── enum_map.zig │ │ ├── parser.zig │ │ ├── method_map.zig │ │ ├── sysfd.zig │ │ ├── testrunner.zig │ │ └── print.zig │ ├── standard │ │ ├── net │ │ │ ├── http │ │ │ │ └── lib.zig │ │ │ └── lib.zig │ │ ├── lib.zig │ │ ├── serde │ │ │ ├── base64.zig │ │ │ ├── brotli.zig │ │ │ ├── zstd.zig │ │ │ ├── gzip.zig │ │ │ ├── flate.zig │ │ │ ├── zlib.zig │ │ │ ├── lz4.zig │ │ │ └── lib.zig │ │ ├── crypto │ │ │ ├── uuid.zig │ │ │ ├── random.zig │ │ │ ├── common.zig │ │ │ └── password.zig │ │ ├── require.zig │ │ └── platform │ │ │ ├── memory.zig │ │ │ └── lib.zig │ ├── objects │ │ └── lib.zig │ └── runtime │ │ └── profiler.zig ├── cli.zig ├── tagged.zig ├── libraries │ └── toml.zig └── commands │ ├── lib.zig │ ├── help.zig │ ├── info.zig │ ├── luau.zig │ ├── init.zig │ └── repl │ └── History.zig ├── .gitattributes ├── legacy ├── compress.zig └── compress │ ├── flate │ ├── consts.zig │ ├── bit_writer.zig │ └── Lookup.zig │ ├── gzip.zig │ └── zlib.zig ├── .gitignore ├── prebuild ├── bytecode.zig └── compressor.zig ├── README.md ├── CONTRIBUTING.md ├── LICENSE.md └── .github └── workflows ├── ci.yaml ├── release.yaml └── pre-release.yaml /test/engine/generated/blank.luau: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/engine/generated/v2.luau: -------------------------------------------------------------------------------- 1 | return 1; 2 | -------------------------------------------------------------------------------- /test/engine/generated/v4.luau: -------------------------------------------------------------------------------- 1 | return true; -------------------------------------------------------------------------------- /test/engine/legacy/A.lua: -------------------------------------------------------------------------------- 1 | return "A-Legacy" -------------------------------------------------------------------------------- /test/cli/run.luau: -------------------------------------------------------------------------------- 1 | print("Hello, world!") 2 | -------------------------------------------------------------------------------- /test/engine/module/init.luau: -------------------------------------------------------------------------------- 1 | return "FullModule" -------------------------------------------------------------------------------- /test/engine/samples/Utils/utilB.luau: -------------------------------------------------------------------------------- 1 | return nil; -------------------------------------------------------------------------------- /test/engine/samples/anyvalue.luau: -------------------------------------------------------------------------------- 1 | return "Zune" -------------------------------------------------------------------------------- /test/engine/generated/duplicate.lua: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/duplicate.luau: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/duplicate2.lua: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/duplicate2.luau: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/duplicate3.luau: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/standard/require/module/bar.luau: -------------------------------------------------------------------------------- 1 | return {"bar"}; -------------------------------------------------------------------------------- /test/standard/require/module/foo.luau: -------------------------------------------------------------------------------- 1 | return {"foo"}; -------------------------------------------------------------------------------- /test/engine/generated/duplicate2/init.luau: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/duplicate3/init.luau: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /test/engine/generated/v3.luau: -------------------------------------------------------------------------------- 1 | return "Hello, World!"; 2 | -------------------------------------------------------------------------------- /test/engine/legacy/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "Legacy" 3 | } -------------------------------------------------------------------------------- /test/engine/generated/v1.luau: -------------------------------------------------------------------------------- 1 | return buffer.create(32); 2 | -------------------------------------------------------------------------------- /test/engine/sublibrary/inner.luau: -------------------------------------------------------------------------------- 1 | return "inner file kool" 2 | -------------------------------------------------------------------------------- /test/standard/thread/module.luau: -------------------------------------------------------------------------------- 1 | print("this is a module"); 2 | -------------------------------------------------------------------------------- /test/engine/sublibrary/subdir/inner.luau: -------------------------------------------------------------------------------- 1 | return "inner file foo" 2 | -------------------------------------------------------------------------------- /src/core/utils/lib.zig: -------------------------------------------------------------------------------- 1 | test { 2 | _ = @import("./lists.zig"); 3 | } 4 | -------------------------------------------------------------------------------- /test/engine/generated/toomany.luau: -------------------------------------------------------------------------------- 1 | return "Hello, World!", "String 2"; 2 | -------------------------------------------------------------------------------- /test/engine/module/withinit/child.luau: -------------------------------------------------------------------------------- 1 | return { 2 | t = "child", 3 | } 4 | -------------------------------------------------------------------------------- /test/engine/module/withinit_bad/child.luau: -------------------------------------------------------------------------------- 1 | return { 2 | t = "child", 3 | } 4 | -------------------------------------------------------------------------------- /test/engine/samples/cyclic.luau: -------------------------------------------------------------------------------- 1 | local a = require("./cyclic2"); 2 | 3 | return {}; -------------------------------------------------------------------------------- /test/engine/samples/cyclic2.luau: -------------------------------------------------------------------------------- 1 | local b = require("./cyclic"); 2 | 3 | return {}; -------------------------------------------------------------------------------- /test/engine/generated/relative.luau: -------------------------------------------------------------------------------- 1 | return function() 2 | return require("./v1"); 3 | end 4 | -------------------------------------------------------------------------------- /test/engine/generated/v5.luau: -------------------------------------------------------------------------------- 1 | local task = zune.task; 2 | 3 | task.wait(1); 4 | 5 | return true; -------------------------------------------------------------------------------- /test/engine/generated/v6.luau: -------------------------------------------------------------------------------- 1 | local task = zune.task; 2 | 3 | task.wait(1); 4 | 5 | return {}; -------------------------------------------------------------------------------- /test/engine/dev.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Boolean = true, 3 | Number = 1, 4 | String = "string", 5 | } -------------------------------------------------------------------------------- /test/cli/bundle.luau: -------------------------------------------------------------------------------- 1 | zune.io.stdout:writeSync(`from bundled: {#zune.fs.embeddedScripts()}+{#zune.fs.embeddedFiles()}`); -------------------------------------------------------------------------------- /test/engine/sublibrary/subdir/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": { 3 | "foo": "./foo", 4 | "inner_foo": "./inner" 5 | } 6 | } -------------------------------------------------------------------------------- /test/engine/sublibrary/kool.luau: -------------------------------------------------------------------------------- 1 | local inner = require("@inner") 2 | assert(inner == "inner file kool") 3 | return "Sub library" 4 | -------------------------------------------------------------------------------- /test/engine/generated/yieldtoomany.luau: -------------------------------------------------------------------------------- 1 | local task = zune.task; 2 | 3 | task.wait(1); 4 | 5 | return "Hello, World!", "String 2"; 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Use LF 5 | test/**/*.txt eol=lf 6 | *.zig text eol=lf -------------------------------------------------------------------------------- /test/engine/sublibrary/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": { 3 | "kool": "./kool", 4 | "sub": "./subdir", 5 | "inner": "./inner" 6 | } 7 | } -------------------------------------------------------------------------------- /test/standard/ffi/zune.toml: -------------------------------------------------------------------------------- 1 | # test configuration 2 | 3 | [runtime.debug] 4 | detailedError = true 5 | 6 | [features.builtins] 7 | ffi = true 8 | -------------------------------------------------------------------------------- /test/standard/sqlite/zune.toml: -------------------------------------------------------------------------------- 1 | # test configuration 2 | 3 | [runtime.debug] 4 | detailedError = true 5 | 6 | [features.builtins] 7 | sqlite = true 8 | -------------------------------------------------------------------------------- /test/engine/generated/yielderror.luau: -------------------------------------------------------------------------------- 1 | local task = zune.task; 2 | 3 | task.wait(1); 4 | 5 | error("This is an error in async require"); 6 | 7 | return true; -------------------------------------------------------------------------------- /test/engine/module/sub.luau: -------------------------------------------------------------------------------- 1 | return { 2 | foo = function() 3 | return "foo" 4 | end, 5 | bar = function() 6 | return "bar" 7 | end, 8 | } -------------------------------------------------------------------------------- /legacy/compress.zig: -------------------------------------------------------------------------------- 1 | pub const flate = @import("compress/flate.zig"); 2 | pub const gzip = @import("compress/gzip.zig"); 3 | pub const zlib = @import("compress/zlib.zig"); 4 | -------------------------------------------------------------------------------- /test/engine/module/withinit/init.luau: -------------------------------------------------------------------------------- 1 | local sibling = require("./sub") 2 | local child = require("@self/child") 3 | 4 | return { 5 | sibling = sibling, 6 | child = child, 7 | } 8 | -------------------------------------------------------------------------------- /test/engine/module/withinit_bad/init.luau: -------------------------------------------------------------------------------- 1 | local sibling = require("@self/sub") 2 | local child = require("./child") 3 | 4 | return { 5 | sibling = sibling, 6 | child = child, 7 | } 8 | -------------------------------------------------------------------------------- /test/engine/module/withinit/descendant/init.luau: -------------------------------------------------------------------------------- 1 | local sibling = require("./child") 2 | local parent_sibling = require("../sub") 3 | 4 | return { 5 | sibling = sibling, 6 | parent_sibling = parent_sibling, 7 | } 8 | -------------------------------------------------------------------------------- /test/engine/sublibrary/subdir/foo.luau: -------------------------------------------------------------------------------- 1 | local inner_foo = require("@inner_foo") 2 | local inner_kool = require("@inner") 3 | assert(inner_foo == "inner file foo") 4 | assert(inner_kool == "inner file kool") 5 | return "The bar exam" 6 | -------------------------------------------------------------------------------- /test/engine/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": { 3 | "module": "./module", 4 | "dev": "./dev", 5 | "sub": "./sublibrary", 6 | "subd": "./sublibrary/subdir", 7 | "alias_path": "@test", 8 | "unknown_path": "test" 9 | } 10 | } -------------------------------------------------------------------------------- /test/zune.test.luau: -------------------------------------------------------------------------------- 1 | assert(zune, "Zune is not defined"); 2 | 3 | local success, err = pcall(function() 4 | zune.fs = {} :: any; 5 | end) 6 | assert(not success, "Zune should be readonly"); 7 | assert(err:find("readonly"), "Zune should be readonly"); 8 | -------------------------------------------------------------------------------- /test/cli/test.luau: -------------------------------------------------------------------------------- 1 | local testing = zune.testing; 2 | 3 | local describe = testing.describe; 4 | local expect = testing.expect; 5 | local test = testing.test; 6 | 7 | describe("CLI", function() 8 | test("Basic", function() 9 | expect(1).toBe(1); 10 | end) 11 | end) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Zig build artifacts 2 | .zig-cache/ 3 | **/zig-out/ 4 | 5 | # macOs Directory 6 | **/.DS_Store 7 | 8 | **/**.luac 9 | **/**.luauc 10 | **/**.gz 11 | 12 | # Vscode 13 | .vscode/ 14 | # Zed 15 | .zed/ 16 | # Neovim 17 | .nvim.lua 18 | .nvimrc 19 | .nvim/ 20 | # Emacs 21 | .dir-locals.el 22 | # Nix Environment 23 | shell.nix 24 | .direnv 25 | .envrc 26 | -------------------------------------------------------------------------------- /src/core/utils/old_writer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn adaptToOldInterface(r: *std.Io.Writer) std.Io.AnyWriter { 4 | return .{ .context = r, .writeFn = derpWrite }; 5 | } 6 | 7 | fn derpWrite(context: *const anyopaque, buffer: []const u8) anyerror!usize { 8 | const w: *std.Io.Writer = @ptrCast(@alignCast(@constCast(context))); 9 | return w.write(buffer); 10 | } 11 | -------------------------------------------------------------------------------- /src/core/utils/enum_map.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn Gen(comptime Enum: type) std.StaticStringMap(Enum) { 4 | const enum_data = @typeInfo(Enum).@"enum"; 5 | var list: [enum_data.fields.len]struct { []const u8, Enum } = undefined; 6 | 7 | for (enum_data.fields, 0..) |field, i| { 8 | list[i] = .{ field.name, @enumFromInt(field.value) }; 9 | } 10 | 11 | return std.StaticStringMap(Enum).initComptime(list); 12 | } 13 | -------------------------------------------------------------------------------- /src/core/standard/net/http/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const builtin = @import("builtin"); 4 | 5 | const VM = luau.VM; 6 | 7 | pub fn load(L: *VM.lua.State) !void { 8 | try @import("server/lib.zig").lua_load(L); 9 | try @import("websocket/lib.zig").lua_load(L); 10 | try L.Zpushvalue(.{ 11 | .serve = @import("server/lib.zig").lua_serve, 12 | .request = @import("client/lib.zig").lua_request, 13 | .websocket = @import("websocket/lib.zig").lua_websocket, 14 | }); 15 | L.setreadonly(-1, true); 16 | } 17 | -------------------------------------------------------------------------------- /test/engine/samples/Utils/utilA.luau: -------------------------------------------------------------------------------- 1 | local utilB = require("./utilB"); 2 | 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("utilB", function() 10 | test("Validating", function() 11 | expect(utilB).toBeNil(); 12 | end) 13 | end) 14 | 15 | describe("utilA", function() 16 | test("Validating Context", function() 17 | expect(_FILE).never.toBeNil(); 18 | expect(debug.info(1, "s"):find("utilA%.luau")).never.toBeNil(); 19 | end) 20 | end) 21 | 22 | local util = {}; 23 | 24 | return util; -------------------------------------------------------------------------------- /src/cli.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Zune = @import("zune"); 4 | 5 | const Commands = @import("commands/lib.zig"); 6 | 7 | const CommandMap = Commands.CommandMap; 8 | 9 | pub fn start(args: [][:0]u8) !void { 10 | if (args.len < 2) { 11 | const command = CommandMap.get("help") orelse @panic("Help command missing."); 12 | return command.execute(Zune.DEFAULT_ALLOCATOR, &.{}); 13 | } 14 | 15 | if (CommandMap.get(args[1])) |command| 16 | return command.execute(Zune.DEFAULT_ALLOCATOR, args[2..]); 17 | 18 | std.debug.print("unknown command, try 'help' or '-h'\n", .{}); 19 | std.process.exit(1); 20 | } 21 | 22 | test { 23 | _ = Commands; 24 | } 25 | -------------------------------------------------------------------------------- /test/standard/net/init.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local testing = zune.testing; 3 | 4 | local describe = testing.describe; 5 | 6 | local _, version, _ = string.match(_VERSION, "(zune) (%d+%.%d+%.%d+.*)+(%d+%.%d+)"); 7 | assert(version and #version > 0, "No version"); 8 | 9 | describe("Socket", function() 10 | require("@self/socket.test"); 11 | end) 12 | 13 | describe("Http", function() 14 | describe("Server", function() 15 | require("@self/http/server.test"); 16 | end) 17 | 18 | describe("Requests", function() 19 | require("@self/http/request.test"); 20 | end) 21 | 22 | describe("WebSockets", function() 23 | require("@self/http/websocket.test"); 24 | end) 25 | end) 26 | -------------------------------------------------------------------------------- /test/standard/serde/base64.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Base64", function() 10 | test("Encoding", function() 11 | expect(serde.base64.encode("Hello, World!")).toBe("SGVsbG8sIFdvcmxkIQ=="); 12 | end) 13 | 14 | test("Decoding", function() 15 | expect(serde.base64.decode("SGVsbG8sIFdvcmxkIQ==")).toBe("Hello, World!"); 16 | end) 17 | 18 | test("Error", function() 19 | expect(function() 20 | serde.base64.decode("InvalidBase64String") 21 | end).toThrow("InvalidPadding"); 22 | end) 23 | end) 24 | 25 | return nil; 26 | -------------------------------------------------------------------------------- /test/engine/samples/simpleLib.luau: -------------------------------------------------------------------------------- 1 | local utilA = require("./Utils/utilA"); 2 | local constant = require("./anyvalue"); 3 | 4 | local testing = zune.testing; 5 | 6 | local describe = testing.describe; 7 | local expect = testing.expect; 8 | local test = testing.test; 9 | 10 | describe("utilA", function() 11 | test("Validating", function() 12 | expect(utilA).toBe(expect.any("table")); 13 | local i, v = next(utilA); 14 | expect(i).toBeNil(); 15 | expect(v).toBeNil(); 16 | end) 17 | end) 18 | 19 | test("Constant", function() 20 | expect(constant).toBe(expect.type("string")); 21 | expect(constant).toBe("Zune"); 22 | end) 23 | 24 | describe("simpleLib", function() 25 | test("Validating Context", function() 26 | expect(_FILE).never.toBeNil(); 27 | expect(debug.info(1, "s"):find("simpleLib%.luau")).never.toBeNil(); 28 | end) 29 | end) 30 | 31 | local simple = {}; 32 | 33 | simple.size = 32; 34 | simple.buffer = buffer.create(simple.size); 35 | 36 | function simple:emit() 37 | return "Hello, World!"; 38 | end 39 | 40 | return simple; -------------------------------------------------------------------------------- /prebuild/bytecode.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | pub fn main() !void { 5 | const allocator = std.heap.page_allocator; 6 | const args = try std.process.argsAlloc(allocator); 7 | defer std.process.argsFree(allocator, args); 8 | 9 | if (args.len < 3) { 10 | try std.fs.File.stderr().writeAll("Usage: \n"); 11 | std.process.exit(1); 12 | } 13 | 14 | const luau_file = try std.fs.openFileAbsolute(args[1], .{ 15 | .mode = .read_only, 16 | }); 17 | defer luau_file.close(); 18 | 19 | const luau_source = try luau_file.readToEndAlloc(allocator, std.math.maxInt(usize)); 20 | defer allocator.free(luau_source); 21 | 22 | const luau_bytecode = try luau.compile(allocator, luau_source, luau.CompileOptions{ 23 | .debugLevel = 1, 24 | .optimizationLevel = 2, 25 | }); 26 | 27 | const luau_bytecode_path = try std.fs.createFileAbsolute(args[2], .{}); 28 | defer luau_bytecode_path.close(); 29 | 30 | try luau_bytecode_path.writeAll(luau_bytecode); 31 | } 32 | -------------------------------------------------------------------------------- /src/core/objects/lib.zig: -------------------------------------------------------------------------------- 1 | const luau = @import("luau"); 2 | 3 | const VM = luau.VM; 4 | 5 | pub const filesystem = struct { 6 | pub const File = @import("filesystem/File.zig"); 7 | }; 8 | 9 | pub const network = struct { 10 | pub const Socket = @import("network/Socket.zig"); 11 | }; 12 | 13 | pub const process = struct { 14 | pub const Child = @import("process/Child.zig"); 15 | }; 16 | 17 | fn loadNamespace(comptime ns: type, L: *VM.lua.State) !void { 18 | inline for (@typeInfo(ns).@"struct".decls) |field| { 19 | const object = @field(ns, field.name); 20 | if (comptime !object.PlatformSupported()) 21 | continue; 22 | if (@hasDecl(object, "load")) { 23 | try object.load(L); 24 | } 25 | } 26 | } 27 | 28 | pub fn load(L: *VM.lua.State) !void { 29 | inline for (@typeInfo(@This()).@"struct".decls) |field| { 30 | const ns = @field(@This(), field.name); 31 | switch (@typeInfo(@TypeOf(ns))) { 32 | .type => try loadNamespace(ns, L), 33 | else => continue, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/tagged.zig: -------------------------------------------------------------------------------- 1 | const TagNames: []const []const u8 = &.{ 2 | // FS 3 | "FS_FILE", 4 | "FS_WATCHER", 5 | // NET 6 | "NET_SOCKET", 7 | "NET_HTTP_SERVER", 8 | "NET_HTTP_WEBSOCKET", 9 | "NET_HTTP_CLIENTWEBSOCKET", 10 | // IO 11 | "IO_STREAM", 12 | "IO_BUFFERSINK", 13 | "IO_BUFFERSTREAM", 14 | // DATETIME 15 | "DATETIME", 16 | // PROCESS 17 | "PROCESS_CHILD", 18 | // CRYPTO 19 | "CRYPTO_HASHER", 20 | "CRYPTO_TLS_CERTBUNDLE", 21 | "CRYPTO_TLS_CERTKEYPAIR", 22 | // RANDOM 23 | "RANDOM", 24 | // REGEX 25 | "REGEX_COMPILED", 26 | // FFI 27 | "FFI_LIBRARY", 28 | "FFI_POINTER", 29 | "FFI_DATATYPE", 30 | // SQLITE 31 | "SQLITE_DATABASE", 32 | "SQLITE_STATEMENT", 33 | // THREAD 34 | "THREAD", 35 | }; 36 | 37 | pub const Tags = block_name: { 38 | const std = @import("std"); 39 | 40 | var list: [TagNames.len]struct { []const u8, comptime_int } = undefined; 41 | 42 | for (TagNames, 0..) |name, i| { 43 | list[i] = .{ name, i + 1 }; 44 | } 45 | 46 | break :block_name std.StaticStringMap(comptime_int).initComptime(list); 47 | }; 48 | -------------------------------------------------------------------------------- /src/core/standard/lib.zig: -------------------------------------------------------------------------------- 1 | pub const fs = @import("fs/lib.zig"); 2 | pub const io = @import("io.zig"); 3 | pub const ffi = @import("ffi.zig"); 4 | pub const mem = @import("mem.zig"); 5 | pub const net = @import("net/lib.zig"); 6 | pub const task = @import("task.zig"); 7 | pub const luau = @import("luau.zig"); 8 | pub const time = @import("time/lib.zig"); 9 | pub const regex = @import("regex.zig"); 10 | pub const serde = @import("serde/lib.zig"); 11 | pub const sqlite = @import("sqlite.zig"); 12 | pub const thread = @import("thread.zig"); 13 | pub const crypto = @import("crypto/lib.zig"); 14 | pub const random = @import("random.zig"); 15 | pub const process = @import("process.zig"); 16 | pub const require = @import("require.zig"); 17 | pub const testing = @import("testing.zig"); 18 | pub const platform = @import("platform/lib.zig"); 19 | 20 | test { 21 | _ = fs; 22 | _ = process; 23 | _ = testing; 24 | _ = task; 25 | _ = net; 26 | _ = luau; 27 | _ = serde; 28 | _ = io; 29 | _ = crypto; 30 | _ = regex; 31 | _ = time; 32 | _ = ffi; 33 | _ = sqlite; 34 | _ = require; 35 | _ = random; 36 | _ = thread; 37 | _ = mem; 38 | _ = platform; 39 | } 40 | -------------------------------------------------------------------------------- /test/standard/crypto/uuid.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local crypto = zune.crypto; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("UUID", function() 10 | test("v4", function() 11 | local uuid = crypto.uuid.v4(); 12 | expect(uuid).toBe(expect.type("string")); 13 | expect(#uuid).toBe(36); 14 | local split = string.split(uuid, "-"); 15 | expect(#split).toBe(5); 16 | expect(#split[1]).toBe(8); 17 | expect(#split[2]).toBe(4); 18 | expect(#split[3]).toBe(4); 19 | expect(#split[4]).toBe(4); 20 | expect(#split[5]).toBe(12); 21 | end) 22 | 23 | test("v7", function() 24 | local uuid = crypto.uuid.v7(); 25 | expect(uuid).toBe(expect.type("string")); 26 | expect(#uuid).toBe(36); 27 | local split = string.split(uuid, "-"); 28 | expect(#split).toBe(5); 29 | expect(#split[1]).toBe(8); 30 | expect(#split[2]).toBe(4); 31 | expect(#split[3]).toBe(4); 32 | expect(#split[4]).toBe(4); 33 | expect(#split[5]).toBe(12); 34 | end) 35 | end) 36 | 37 | return nil; -------------------------------------------------------------------------------- /src/core/standard/serde/base64.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const yaml = @import("yaml"); 3 | const luau = @import("luau"); 4 | 5 | const VM = luau.VM; 6 | 7 | pub fn lua_encode(L: *VM.lua.State) !i32 { 8 | const string = try L.Zcheckvalue([]const u8, 1, null); 9 | 10 | const allocator = luau.getallocator(L); 11 | 12 | const out = try allocator.alloc(u8, std.base64.standard.Encoder.calcSize(string.len)); 13 | defer allocator.free(out); 14 | 15 | const encoded = std.base64.standard.Encoder.encode(out, string); 16 | 17 | if (L.typeOf(1) == .Buffer) 18 | try L.Zpushbuffer(encoded) 19 | else 20 | try L.pushlstring(encoded); 21 | 22 | return 1; 23 | } 24 | 25 | pub fn lua_decode(L: *VM.lua.State) !i32 { 26 | const string = try L.Zcheckvalue([]const u8, 1, null); 27 | 28 | const allocator = luau.getallocator(L); 29 | 30 | const out = try allocator.alloc(u8, try std.base64.standard.Decoder.calcSizeForSlice(string)); 31 | defer allocator.free(out); 32 | 33 | try std.base64.standard.Decoder.decode(out, string); 34 | 35 | if (L.typeOf(1) == .Buffer) 36 | try L.Zpushbuffer(out) 37 | else 38 | try L.pushlstring(out); 39 | 40 | return 1; 41 | } 42 | -------------------------------------------------------------------------------- /src/core/utils/parser.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const LineInfo = struct { 4 | line: usize, 5 | col: usize, 6 | }; 7 | 8 | pub fn getLineInfo(string: []const u8, pos: usize) LineInfo { 9 | var line: usize = 1; 10 | var col: usize = 0; 11 | for (0..pos) |p| { 12 | if (p >= string.len) 13 | break; 14 | switch (string[p]) { 15 | '\n' => { 16 | line += 1; 17 | col = 0; 18 | }, 19 | else => col += 1, 20 | } 21 | } 22 | return .{ .line = line, .col = col }; 23 | } 24 | 25 | pub fn nextNonCharacter(slice: []const u8, comptime characters: []const u8) usize { 26 | loop: for (slice, 0..) |c, p| { 27 | for (characters) |b| 28 | if (b == c) 29 | continue :loop; 30 | return p; 31 | } 32 | return slice.len; 33 | } 34 | 35 | pub fn nextCharacter(slice: []const u8, comptime characters: []const u8) usize { 36 | for (slice, 0..) |c, p| 37 | for (characters) |b| 38 | if (b == c) 39 | return p; 40 | return slice.len; 41 | } 42 | 43 | pub fn trimSpace(slice: []const u8) []const u8 { 44 | return std.mem.trim(u8, slice, " \t\r"); 45 | } 46 | 47 | pub fn isPlainText(slice: []const u8) bool { 48 | for (0..slice.len) |i| { 49 | switch (slice[i]) { 50 | 'A'...'Z', 'a'...'z', '_' => {}, 51 | '0'...'9' => if (i == 0) return false, 52 | else => return false, 53 | } 54 | } 55 | return true; 56 | } 57 | -------------------------------------------------------------------------------- /prebuild/compressor.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lcompress = @import("lcompress"); 3 | 4 | pub fn adaptToOldInterface(r: *std.Io.Writer) std.Io.AnyWriter { 5 | return .{ .context = r, .writeFn = derpWrite }; 6 | } 7 | 8 | fn derpWrite(context: *const anyopaque, buffer: []const u8) anyerror!usize { 9 | const w: *std.Io.Writer = @ptrCast(@alignCast(@constCast(context))); 10 | return w.write(buffer); 11 | } 12 | 13 | pub fn main() !void { 14 | const allocator = std.heap.page_allocator; 15 | const args = try std.process.argsAlloc(allocator); 16 | defer std.process.argsFree(allocator, args); 17 | 18 | if (args.len < 3) { 19 | try std.fs.File.stderr().writeAll("Usage: \n"); 20 | std.process.exit(1); 21 | } 22 | 23 | const file = try std.fs.openFileAbsolute(args[1], .{ 24 | .mode = .read_only, 25 | }); 26 | defer file.close(); 27 | 28 | var read_buffer: [4096]u8 = undefined; 29 | var file_reader = file.reader(&read_buffer); 30 | 31 | const reader = &file_reader.interface; 32 | const old_reader = reader.adaptToOldInterface(); 33 | 34 | const compressed_file = try std.fs.createFileAbsolute(args[2], .{}); 35 | defer compressed_file.close(); 36 | 37 | var write_buffer: [4096]u8 = undefined; 38 | var file_writer = compressed_file.writer(&write_buffer); 39 | 40 | const writer = &file_writer.interface; 41 | const old_writer = adaptToOldInterface(writer); 42 | 43 | try lcompress.gzip.compress(old_reader, old_writer, .{}); 44 | try writer.flush(); 45 | } 46 | -------------------------------------------------------------------------------- /src/core/standard/crypto/uuid.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const VM = luau.VM; 5 | 6 | const UUID = u128; 7 | 8 | fn format(uuid: UUID, buf: []u8) void { 9 | std.debug.assert(buf.len >= 36); 10 | _ = std.fmt.bufPrint(buf, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ 11 | @byteSwap(@as(u32, @intCast(uuid & 0xFFFFFFFF))), 12 | @byteSwap(@as(u16, @intCast((uuid >> 32) & 0xFFFF))), 13 | @byteSwap(@as(u16, @intCast((uuid >> 48) & 0xFFFF))), 14 | @as(u8, @intCast((uuid >> 64) & 0xFF)), 15 | @as(u8, @intCast((uuid >> 72) & 0xFF)), 16 | @byteSwap(@as(u48, @intCast((uuid >> 80) & 0xFFFFFFFFFFFF))), 17 | }) catch unreachable; 18 | } 19 | 20 | pub fn lua_v4(L: *VM.lua.State) !i32 { 21 | var uuid: u128 = std.crypto.random.int(UUID); 22 | 23 | uuid &= 0xFFFFFFFFFFFFFF3FFF0FFFFFFFFFFFFF; 24 | uuid |= 0x00000000000000800040000000000000; 25 | 26 | var buf: [36]u8 = undefined; 27 | 28 | format(uuid, buf[0..]); 29 | 30 | try L.pushlstring(buf[0..]); 31 | 32 | return 1; 33 | } 34 | 35 | pub fn lua_v7(L: *VM.lua.State) !i32 { 36 | const ms = @as(u48, @truncate(@as(u64, @intCast(std.time.milliTimestamp())) & 0xFFFFFFFFFFFF)); 37 | 38 | var uuid: UUID = @as(UUID, @intCast(std.crypto.random.int(u80))) << 48; 39 | 40 | uuid |= @as(UUID, @intCast(@byteSwap(ms))); 41 | uuid &= 0xFFFFFFFFFFFFFF3FFF0FFFFFFFFFFFFF; 42 | uuid |= 0x00000000000000800070000000000000; 43 | 44 | var buf: [36]u8 = undefined; 45 | 46 | format(uuid, buf[0..]); 47 | 48 | try L.pushlstring(buf[0..]); 49 | 50 | return 1; 51 | } 52 | -------------------------------------------------------------------------------- /test/standard/platform.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local testing = zune.testing; 3 | local platform = zune.platform; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("platform", function() 10 | test("Properties", function() 11 | expect(platform.os).toBe(expect.any("string")); 12 | expect(#platform.os).toBeGreaterThan(0); 13 | 14 | expect(platform.cpu.arch).toBe(expect.any("string")); 15 | expect(#platform.cpu.arch).toBeGreaterThan(0); 16 | expect(platform.cpu.endian).toBe(expect.any("string")); 17 | expect(#platform.cpu.endian).toBeGreaterThan(0); 18 | expect(table.find({"big", "little"}, platform.cpu.endian) ~= nil).toBeTruthy(); 19 | 20 | expect(table.find({"io_uring", "epoll", "kqueue", "iocp"}, platform.async) ~= nil).toBeTruthy(); 21 | end) 22 | 23 | test("Memory", function() 24 | local total_memory = platform.getTotalMemory(); 25 | expect(total_memory).toBe(expect.any("number")); 26 | expect(total_memory).toBeGreaterThan(0); 27 | 28 | local free_memory = platform.getFreeMemory(); 29 | expect(free_memory).toBe(expect.any("number")); 30 | expect(free_memory).toBeGreaterThan(0); 31 | end) 32 | 33 | test("HostName", function() 34 | local host_name = platform.getHostName(); 35 | expect(host_name).toBe(expect.any("string")); 36 | expect(#host_name).toBeGreaterThan(0); 37 | end) 38 | 39 | test("Kernel Version", function() 40 | local kernel_version = platform.getKernelVersion(); 41 | expect(kernel_version).toBe(expect.any("string")); 42 | expect(#kernel_version).toBeGreaterThan(0); 43 | end) 44 | end) -------------------------------------------------------------------------------- /src/libraries/toml.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const toml = @import("toml"); 3 | 4 | pub const parse = toml.parse; 5 | 6 | pub fn checkTable(table: toml.Table, comptime key: []const u8) ?toml.Table { 7 | if (!table.contains(key)) return null; 8 | const item = table.table.get(key) orelse unreachable; 9 | if (item != .table) 10 | std.debug.panic("[zune.toml] '{s}' must be a table\n", .{key}); 11 | return item.table; 12 | } 13 | 14 | pub fn checkArray(table: toml.Table, comptime key: []const u8) ?toml.Array { 15 | if (!table.contains(key)) return null; 16 | const item = table.table.get(key) orelse unreachable; 17 | if (item != .array) 18 | std.debug.panic("[zune.toml] '{s}' must be a table\n", .{key}); 19 | return item.array; 20 | } 21 | 22 | pub fn checkInteger(table: toml.Table, comptime key: []const u8) ?i64 { 23 | if (!table.contains(key)) return null; 24 | const item = table.table.get(key) orelse unreachable; 25 | if (item != .integer) 26 | std.debug.panic("[zune.toml] '{s}' must be a integer\n", .{key}); 27 | return item.integer; 28 | } 29 | 30 | pub fn checkBool(table: toml.Table, comptime key: []const u8) ?bool { 31 | if (!table.contains(key)) return null; 32 | const item = table.table.get(key) orelse unreachable; 33 | if (item != .boolean) 34 | std.debug.panic("[zune.toml] '{s}' must be a boolean\n", .{key}); 35 | return item.boolean; 36 | } 37 | 38 | pub fn checkString(table: toml.Table, comptime key: []const u8) ?[]const u8 { 39 | if (!table.contains(key)) return null; 40 | const item = table.table.get(key) orelse unreachable; 41 | if (item != .string) 42 | std.debug.panic("[zune.toml] '{s}' must be a string\n", .{key}); 43 | return item.string; 44 | } 45 | -------------------------------------------------------------------------------- /src/core/standard/crypto/random.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const common = @import("common.zig"); 5 | 6 | const VM = luau.VM; 7 | 8 | pub fn lua_boolean(L: *VM.lua.State) i32 { 9 | L.pushboolean(std.crypto.random.boolean()); 10 | return 1; 11 | } 12 | 13 | pub fn lua_nextinteger(L: *VM.lua.State) !i32 { 14 | const min = L.tointeger(1) orelse { 15 | L.pushinteger(std.crypto.random.int(i32)); 16 | return 1; 17 | }; 18 | const max = try L.Zcheckvalue(i32, 2, null); 19 | if (min > max) 20 | return L.Zerror("invalid range (min > max)"); 21 | L.pushinteger(std.crypto.random.intRangeAtMost(i32, min, max)); 22 | return 1; 23 | } 24 | 25 | pub fn lua_nextnumber(L: *VM.lua.State) !i32 { 26 | const min = L.tonumber(1) orelse { 27 | L.pushnumber(std.crypto.random.float(f64)); 28 | return 1; 29 | }; 30 | const max = try L.Zcheckvalue(f64, 2, null); 31 | if (min > max) 32 | return L.Zerror("invalid range (min > max)"); 33 | const v = std.crypto.random.float(f64); 34 | L.pushnumber(min + (v * (max - min))); 35 | return 1; 36 | } 37 | 38 | pub fn lua_fill(L: *VM.lua.State) !i32 { 39 | const buffer = try L.Zcheckvalue([]u8, 1, null); 40 | const offset = try L.Zcheckvalue(i32, 2, null); 41 | const length = try L.Zcheckvalue(i32, 3, null); 42 | 43 | if (offset < 0) 44 | return L.Zerror("invalid offset (offset < 0)"); 45 | if (length < 0) 46 | return L.Zerror("invalid length (length < 0)"); 47 | if (offset + length > buffer.len) 48 | return L.Zerror("invalid length (offset + length > buffer size)"); 49 | 50 | std.crypto.random.bytes(buffer[@intCast(offset)..][0..@intCast(length)]); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /test/standard/serde/brotli.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Brotli", function() 10 | local sample = string.rep([[ 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 12 | ]], 20); 13 | local compressed_sample_default = "\x1Bc\x05\xF8\x8D\x93\xE5\x8A\xD3\xA0\x95 \nL\xBA\xCD\xEB\x95?:\x7F\xB1\x11\x83\xAC\xC8\x95\x8D\fdb\"@\x05:\xA8\xCFC\x1Ao\xFD|~fS\x94yBM\x9C]\bA\xFF\x99\x0F^VpO\xC3\xA2z\x10"; 14 | 15 | describe("Compression", function() 16 | test("Default", function() 17 | expect(serde.brotli.compress(sample)).toBe(compressed_sample_default); 18 | end) 19 | test("Buffer (Default)", function() 20 | local buf = serde.brotli.compress(buffer.fromstring(sample)); 21 | expect(buf).toBe(expect.type("buffer")); 22 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 23 | end) 24 | test("Fail", function() 25 | expect(function() serde.brotli.compress(true) end).toThrow("invalid argument #1 to 'compress' (string expected, got boolean)"); 26 | end) 27 | end) 28 | 29 | describe("Decompression", function() 30 | test("Default", function() 31 | expect(serde.brotli.decompress(compressed_sample_default)).toBe(sample); 32 | end) 33 | test("Buffer (Default)", function() 34 | local buf = serde.brotli.decompress(buffer.fromstring(compressed_sample_default)); 35 | expect(buf).toBe(expect.type("buffer")); 36 | expect(buffer.tostring(buf)).toBe(sample); 37 | end) 38 | end) 39 | end) 40 | 41 | return nil; 42 | -------------------------------------------------------------------------------- /src/commands/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Command = struct { 4 | name: []const u8, 5 | execute: *const fn (allocator: std.mem.Allocator, args: []const []const u8) anyerror!void, 6 | aliases: ?[]const []const u8 = null, 7 | }; 8 | 9 | pub fn initCommands(comptime commands: []const Command) std.StaticStringMap(Command) { 10 | var count = 0; 11 | 12 | for (commands) |command| { 13 | if (command.aliases) |aliases| 14 | count += aliases.len; 15 | count += 1; 16 | } 17 | 18 | var list: [count]struct { []const u8, Command } = undefined; 19 | 20 | var i = 0; 21 | for (commands) |command| { 22 | list[i] = .{ command.name, command }; 23 | i += 1; 24 | if (command.aliases) |aliases| 25 | for (aliases) |alias| { 26 | list[i] = .{ alias, command }; 27 | i += 1; 28 | }; 29 | } 30 | 31 | return std.StaticStringMap(Command).initComptime(list); 32 | } 33 | 34 | const Execution = @import("execution.zig"); 35 | 36 | pub const CommandMap = initCommands(&.{ 37 | Execution.RunCmd, 38 | Execution.TestCmd, 39 | Execution.EvalCmd, 40 | Execution.DebugCmd, 41 | @import("setup.zig").Command, 42 | @import("repl/lib.zig").Command, 43 | @import("init.zig").Command, 44 | @import("bundle.zig").Command, 45 | 46 | @import("luau.zig").Command, 47 | @import("help.zig").Command, 48 | 49 | @import("info.zig").Command, 50 | }); 51 | 52 | test { 53 | _ = Execution; 54 | _ = @import("setup.zig"); 55 | _ = @import("repl/lib.zig"); 56 | _ = @import("init.zig"); 57 | _ = @import("bundle.zig"); 58 | _ = @import("luau.zig"); 59 | _ = @import("help.zig"); 60 | _ = @import("info.zig"); 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Zoooooom 3 |

4 |
5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 |

13 | A Luau runtime, similar to Lune, Node, or Bun. 14 |

15 | 16 | ## Features 17 | - **Comprehensive API**: Includes fully featured APIs for filesystem operations, networking, and standard I/O. 18 | - **Rich Standard Library**: A rich standard library with utilities for basic needs, reducing the need for external dependencies. 19 | - **Cross-Platform Compatibility**: Fully compatible with **Linux**, **macOS**, and **Windows**, ensuring broad usability across different operating systems. 20 | - **High Performance**: Built with Zig, designed for high performance and low memory usage. 21 | 22 | ## Links 23 | - [Installation guide](https://zune.sh/guides/install) 24 | - [Compile & build locally](https://zune.sh/guides/install#install-zig) 25 | 26 | ## Roadmap 27 | For more information on the future of zune, check out the milestones 28 | 29 | ## Contributing 30 | Read [CONTRIBUTING.md](https://github.com/Scythe-Technology/zune/blob/master/CONTRIBUTING.md). 31 | -------------------------------------------------------------------------------- /legacy/compress/flate/consts.zig: -------------------------------------------------------------------------------- 1 | pub const deflate = struct { 2 | // Number of tokens to accumulate in deflate before starting block encoding. 3 | // 4 | // In zlib this depends on memlevel: 6 + memlevel, where default memlevel is 5 | // 8 and max 9 that gives 14 or 15 bits. 6 | pub const tokens = 1 << 15; 7 | }; 8 | 9 | pub const match = struct { 10 | pub const base_length = 3; // smallest match length per the RFC section 3.2.5 11 | pub const min_length = 4; // min length used in this algorithm 12 | pub const max_length = 258; 13 | 14 | pub const min_distance = 1; 15 | pub const max_distance = 32768; 16 | }; 17 | 18 | pub const history = struct { 19 | pub const len = match.max_distance; 20 | }; 21 | 22 | pub const lookup = struct { 23 | pub const bits = 15; 24 | pub const len = 1 << bits; 25 | pub const shift = 32 - bits; 26 | }; 27 | 28 | pub const huffman = struct { 29 | // The odd order in which the codegen code sizes are written. 30 | pub const codegen_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; 31 | // The number of codegen codes. 32 | pub const codegen_code_count = 19; 33 | 34 | // The largest distance code. 35 | pub const distance_code_count = 30; 36 | 37 | // Maximum number of literals. 38 | pub const max_num_lit = 286; 39 | 40 | // Max number of frequencies used for a Huffman Code 41 | // Possible lengths are codegen_code_count (19), distance_code_count (30) and max_num_lit (286). 42 | // The largest of these is max_num_lit. 43 | pub const max_num_frequencies = max_num_lit; 44 | 45 | // Biggest block size for uncompressed block. 46 | pub const max_store_block_size = 65535; 47 | // The special code used to mark the end of a block. 48 | pub const end_block_marker = 256; 49 | }; 50 | -------------------------------------------------------------------------------- /test/standard/require/init.luau: -------------------------------------------------------------------------------- 1 | local testing = zune.testing; 2 | 3 | local describe = testing.describe; 4 | local expect = testing.expect; 5 | local test = testing.test; 6 | 7 | local foo = require("@self/module/foo"); 8 | 9 | local this_path = debug.info(1, "s"); 10 | 11 | local _7 = if zune.platform.os == "windows" then "\\" else "/"; 12 | 13 | describe("require", function() 14 | local RelativeAlias = [[{ 15 | "aliases": { 16 | "test": "./" 17 | } 18 | }]]; 19 | 20 | test("current context", function() 21 | local path = zune.require.navigate("@self/module/foo"); 22 | expect(path).toBe(`test{_7}standard{_7}require{_7}module{_7}foo`); 23 | expect(zune.require.getCached(path)).toBe(expect.similar({"foo"})); 24 | end) 25 | test("navigated require", function() 26 | local path = zune.require.navigate("@self/module/bar"); 27 | local relative = zune.fs.path.relative(zune.fs.path.dirname(this_path) or "./", path); 28 | expect(relative).toBe(`module{_7}bar`); 29 | expect(zune.require.getCached(relative)).toBeNil(); 30 | local bar = require(`./require/{relative}`); 31 | expect(bar).toBe(expect.similar({"bar"})); 32 | expect(zune.require.getCached(path)).toBe(bar); 33 | end) 34 | test("custom config", function() 35 | local path = zune.require.navigate("@test/foo", this_path, RelativeAlias); 36 | expect(path).toBe(`test{_7}standard{_7}foo`); 37 | end) 38 | test("custom config and context", function() 39 | do 40 | local path = zune.require.navigate("@test/foo", "", RelativeAlias); 41 | expect(path).toBe(`foo`); 42 | end 43 | do 44 | local path = zune.require.navigate("@test/foo", "src/main", RelativeAlias); 45 | expect(path).toBe(`src{_7}foo`); 46 | end 47 | end) 48 | end) 49 | 50 | -------------------------------------------------------------------------------- /src/commands/help.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Zune = @import("zune"); 4 | 5 | const command = @import("lib.zig"); 6 | 7 | fn Execute(_: std.mem.Allocator, _: []const []const u8) !void { 8 | Zune.debug.print("ZUNE - A luau runtime\n" ++ 9 | "\n" ++ 10 | "usage: zune <> [...args]\n" ++ 11 | "\n" ++ 12 | "Commands:\n" ++ 13 | " run ./script.luau Execute lua/luau file.\n" ++ 14 | " test ./test.luau Run tests in lua/luau file, similar to run.\n" ++ 15 | " debug ./script.luau Debug lua/luau file.\n" ++ 16 | " setup [editor] Setup environment for luau-lsp with editor of your choice.\n" ++ 17 | " repl Start REPL session.\n" ++ 18 | " init Create initial files & configs for zune.\n" ++ 19 | " bundle Bundle lua/luau scripts, files with zune as standalone executable.\n" ++ 20 | "\n" ++ 21 | " luau [args...] Display info from luau.\n" ++ 22 | " help Display help message.\n" ++ 23 | " info [all] Display runtime information.\n" ++ 24 | "\n" ++ 25 | "Flags:\n" ++ 26 | " -e, --eval [luau] Evaluate luau code.\n" ++ 27 | " -V, --version Display runtime information.\n" ++ 28 | " -h, --help Display help message.\n" ++ 29 | "", .{}); 30 | } 31 | 32 | pub const Command = command.Command{ 33 | .name = "help", 34 | .execute = Execute, 35 | .aliases = &.{ 36 | "-h", "--help", 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Zune 2 | 3 | This document will guide you through the process of contributing to our project. 4 | 5 | ## Table of Contents 6 | 7 | 1. [How to Contribute](#how-to-contribute) 8 | 2. [Development Environment](#development-environment) 9 | 3. [Submitting a PR](#submitting-changes) 10 | 4. [Thank you](#thank-you-for-contributing) 11 | 12 | ## How to Contribute 13 | 14 | We encourage you to contribute in the following ways: 15 | 16 | - **Reporting Issues:** If you find a bug or have a feature request, please [open an issue](https://github.com/Scythe-Technology/zune/issues). 17 | - **Bugs**: Provide as much detail as needed to help us understand and reproduce the problem. 18 | - **Submitting Pull Requests (PRs):** If you have code changes or improvements, please submit a pull request. Ensure that your code follows our coding standards and includes appropriate tests. We are most likely going to not accept any PRs to cosmetic, style or non-functional changes. 19 | 20 | ## Development Environment 21 | 22 | To get started, follow these steps to set up your environment: 23 | 24 | 1. Clone the repository: 25 | ```sh 26 | git clone https://github.com/Scythe-Technology/zune.git 27 | cd zune 28 | ``` 29 | 2. Install Zig: Ensure you have Zig installed. You can download it from the [official Zig website.](https://ziglang.org/) 30 | 3. Build the Project: 31 | ```sh 32 | zig build 33 | ``` 34 | 4. Run tests: 35 | ```sh 36 | zig build test 37 | ``` 38 | 5. Formatting: 39 | ```sh 40 | zig fmt [file] 41 | # or any other tools, ideally it just needs to be zig formatted. 42 | # and should pass CI. 43 | ``` 44 | 6. Code Style: *N/A (Undecided)*. 45 | 46 | ## Submitting a PR 47 | When submitting a PR, please provide as much detail as possible for the changes when making feature additions. For **bug fixes** or **minor changes**, a brief description is sufficient or use `Fixes #` or `Closes #` in the PR description of the issue the **bug fix** is related to. 48 | 49 | ## Thank you for contributing! 50 | If you have any questions or need help, join the [Sythivorium](https://discord.gg/zEc7muuYbX) server and ask in `#zune` channel. 51 | -------------------------------------------------------------------------------- /src/core/standard/serde/brotli.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const brotli = @import("brotli"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | pub fn lua_compress(L: *VM.lua.State) !i32 { 10 | const allocator = luau.getallocator(L); 11 | 12 | const is_buffer = L.typeOf(1) == .Buffer; 13 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 14 | 15 | var level: u4 = 11; 16 | 17 | if (!L.typeOf(2).isnoneornil()) { 18 | try L.Zchecktype(2, .Table); 19 | const levelType = L.rawgetfield(2, "level"); 20 | if (!levelType.isnoneornil()) { 21 | if (levelType != .Number) 22 | return L.Zerror("options 'level' field must be a number"); 23 | const num = L.tointeger(-1) orelse unreachable; 24 | if (num < 0 or num > 11) 25 | return L.Zerror("options 'level' must not be less than 0 or greater than 11"); 26 | level = @intCast(num); 27 | } 28 | L.pop(1); 29 | } 30 | 31 | const encoder = try brotli.Encoder.init(.{ 32 | .quality = level, 33 | .window = 22, 34 | }); 35 | defer encoder.deinit(); 36 | 37 | const compressed = try encoder.encode(allocator, string); 38 | defer allocator.free(compressed); 39 | 40 | if (is_buffer) try L.Zpushbuffer(compressed) else try L.pushlstring(compressed); 41 | 42 | return 1; 43 | } 44 | 45 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 46 | const allocator = luau.getallocator(L); 47 | 48 | const is_buffer = L.typeOf(1) == .Buffer; 49 | 50 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 51 | 52 | const decoder = try brotli.Decoder.init(.{}); 53 | defer decoder.deinit(); 54 | 55 | const decompressed = try decoder.decode(allocator, string); 56 | defer allocator.free(decompressed); 57 | 58 | if (decompressed.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 59 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 60 | 61 | if (is_buffer) try L.Zpushbuffer(decompressed) else try L.pushlstring(decompressed); 62 | 63 | return 1; 64 | } 65 | -------------------------------------------------------------------------------- /src/core/standard/serde/zstd.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const zstd = @import("zstd"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | pub fn lua_compress(L: *VM.lua.State) !i32 { 10 | const allocator = luau.getallocator(L); 11 | 12 | const is_buffer = L.typeOf(1) == .Buffer; 13 | 14 | const string = if (is_buffer) 15 | L.Lcheckbuffer(1) 16 | else 17 | L.Lcheckstring(1); 18 | 19 | var level: i32 = zstd.DEFAULT_COMPRESSION_LEVEL; 20 | 21 | if (!L.typeOf(2).isnoneornil()) { 22 | try L.Zchecktype(2, .Table); 23 | const levelType = L.rawgetfield(2, "level"); 24 | if (!levelType.isnoneornil()) { 25 | if (levelType != .Number) 26 | return L.Zerror("options 'level' field must be a number"); 27 | const num = L.tointeger(-1) orelse unreachable; 28 | if (num < zstd.MIN_COMPRESSION_LEVEL or num > zstd.MAX_COMPRESSION_LEVEL) 29 | return L.Zerrorf("options 'level' must not be over {} or less than {}", .{ zstd.MAX_COMPRESSION_LEVEL, zstd.MIN_COMPRESSION_LEVEL }); 30 | level = num; 31 | } 32 | L.pop(1); 33 | } 34 | 35 | const compressed = try zstd.compressAlloc(allocator, string, level); 36 | defer allocator.free(compressed); 37 | 38 | if (is_buffer) 39 | try L.Zpushbuffer(compressed) 40 | else 41 | try L.pushlstring(compressed); 42 | 43 | return 1; 44 | } 45 | 46 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 47 | const allocator = luau.getallocator(L); 48 | 49 | const is_buffer = L.typeOf(1) == .Buffer; 50 | 51 | const string = if (is_buffer) 52 | L.Lcheckbuffer(1) 53 | else 54 | L.Lcheckstring(1); 55 | 56 | const decompressed = try zstd.decompressAlloc(allocator, string); 57 | defer allocator.free(decompressed); 58 | 59 | if (decompressed.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 60 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 61 | 62 | if (is_buffer) 63 | try L.Zpushbuffer(decompressed) 64 | else 65 | try L.pushlstring(decompressed); 66 | 67 | return 1; 68 | } 69 | -------------------------------------------------------------------------------- /src/commands/info.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const xev = @import("xev").Dynamic; 3 | const builtin = @import("builtin"); 4 | 5 | const LUAU_VERSION = @import("luau").LUAU_VERSION; 6 | 7 | const Zune = @import("zune"); 8 | 9 | const command = @import("lib.zig"); 10 | 11 | const zune_info = @import("zune-info"); 12 | 13 | fn Execute(_: std.mem.Allocator, args: []const []const u8) !void { 14 | Zune.debug.print("version: {s}\n", .{zune_info.version}); 15 | Zune.debug.print("luau: {d}.{d}\n", .{ LUAU_VERSION.major, LUAU_VERSION.minor }); 16 | Zune.debug.print("async: {t}\n", .{xev.backend}); 17 | Zune.debug.print("platform: {t}-{t}-{t}\n", .{ builtin.cpu.arch, builtin.os.tag, builtin.abi }); 18 | Zune.debug.print("threaded: {} ({} threads)\n", .{ 19 | !builtin.single_threaded, 20 | if (builtin.single_threaded) 1 else std.Thread.getCpuCount() catch 1, 21 | }); 22 | Zune.debug.print("build: {t}\n", .{builtin.mode}); 23 | 24 | if (args.len < 1 or !std.mem.eql(u8, args[0], "all")) 25 | return; 26 | 27 | Zune.debug.print("libraries:\n", .{}); 28 | const longest_name: comptime_int = comptime blk: { 29 | var max = 0; 30 | for (@typeInfo(Zune.corelib).@"struct".decls) |decl| 31 | max = @max(max, decl.name.len); 32 | break :blk max; 33 | }; 34 | inline for (@typeInfo(Zune.corelib).@"struct".decls) |decl| { 35 | const lib = @field(Zune.corelib, decl.name); 36 | const padding = " " ** (longest_name - decl.name.len); 37 | if (@hasDecl(lib, "PlatformSupported")) { 38 | if (lib.PlatformSupported()) 39 | Zune.debug.print(" {s}:{s} supported\n", .{ decl.name, padding }) 40 | else 41 | Zune.debug.print(" {s}:{s} unsupported\n", .{ decl.name, padding }); 42 | } else Zune.debug.print(" {s}:{s} native\n", .{ decl.name, padding }); 43 | } 44 | } 45 | 46 | pub const Command = command.Command{ 47 | .name = "info", 48 | .execute = Execute, 49 | .aliases = &.{ 50 | "-V", 51 | "--version", 52 | "version", 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /test/standard/crypto/init.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | 3 | local crypto = zune.crypto; 4 | local testing = zune.testing; 5 | 6 | local describe = testing.describe; 7 | local expect = testing.expect; 8 | local test = testing.test; 9 | 10 | describe("HashObject", function() 11 | test("Hash", function() 12 | local hasher = crypto.createHash("sha1"); 13 | hasher:update("test"); 14 | expect(hasher:digest("base64")).toBe("qUqP5cyxm6YcTAhz05Hph5gvu9M="); 15 | hasher:update("test"); 16 | expect(hasher:digest("hex")).toBe("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"); 17 | hasher:update("test"); 18 | expect(buffer.tostring(hasher:digest())).toBe("\xA9J\x8F\xE5\xCC\xB1\x9B\xA6\x1CL\bs\xD3\x91\xE9\x87\x98/\xBB\xD3"); 19 | hasher:update("test"); 20 | expect(buffer.tostring(hasher:digest("binary"))).toBe("\xA9J\x8F\xE5\xCC\xB1\x9B\xA6\x1CL\bs\xD3\x91\xE9\x87\x98/\xBB\xD3"); 21 | 22 | expect(hasher:digest("base64")).toBe("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); 23 | expect(hasher:digest("base64")).toBe("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); 24 | 25 | hasher:update("test"); 26 | local copy = hasher:copy(); 27 | expect(copy:digest("base64")).toBe("qUqP5cyxm6YcTAhz05Hph5gvu9M="); 28 | expect(hasher:digest("base64")).toBe("qUqP5cyxm6YcTAhz05Hph5gvu9M="); 29 | end) 30 | 31 | test("Hmac", function() 32 | local hasher = crypto.createHash("sha1", "ez secret"); 33 | hasher:update("test"); 34 | local copy = hasher:copy(); 35 | expect(hasher:digest("base64")).toBe("dLxpVOlznkVJH7UJfDJ0Ms6wO+8="); 36 | expect(function() 37 | hasher:update("test"); 38 | end).toThrow("hasher already used"); 39 | expect(copy:digest("base64")).toBe("dLxpVOlznkVJH7UJfDJ0Ms6wO+8="); 40 | expect(function() 41 | copy:copy(); 42 | end).toThrow("hasher already used"); 43 | end) 44 | end) 45 | 46 | require("@self/hash.test"); 47 | require("@self/hmac.test"); 48 | require("@self/random.test"); 49 | require("@self/uuid.test"); 50 | 51 | describe("aead", function() 52 | require("@self/aead/aegis"); 53 | require("@self/aead/aes_gcm"); 54 | require("@self/aead/aes_ocb"); 55 | require("@self/aead/chacha_poly"); 56 | require("@self/aead/isap"); 57 | require("@self/aead/salsa_poly"); 58 | end) 59 | 60 | require("@self/password.test"); -------------------------------------------------------------------------------- /test/standard/crypto/password.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local crypto = zune.crypto; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | local function testPassword(password: string, options: any?) 10 | local hash; 11 | local hash2; 12 | test("Hash", function() 13 | -- Hashes should be different 14 | hash = crypto.password.hash(password, options); 15 | hash2 = crypto.password.hash(password, options); 16 | expect(hash).never.toBe(hash2); 17 | if (options) then 18 | expect(hash:sub(1, #options.algorithm + 2)).toBe(`${options.algorithm}$`); 19 | end 20 | end) 21 | test("Verification", function() 22 | expect(crypto.password.verify(password, hash)).toBe(true); 23 | expect(crypto.password.verify(password .. 'other', hash)).toBe(false); 24 | end) 25 | end 26 | 27 | describe("Password", function() 28 | describe("Default", function() 29 | testPassword("zune+luau") 30 | end) 31 | 32 | describe("Argon2i", function() 33 | testPassword("zune+luau", {algorithm = "argon2i", threads = 2}) 34 | end) 35 | describe("Argon2id", function() 36 | testPassword("zune+luau", {algorithm = "argon2id", threads = 2}) 37 | end) 38 | describe("Bcrypt", function() 39 | testPassword("zune+luau", {algorithm = "bcrypt"}) 40 | end) 41 | 42 | test("Fail", function() 43 | expect(function() 44 | crypto.password.hash("blank", { 45 | algorithm = 123, 46 | }) 47 | end).toThrow("invalid field 'algorithm' (string expected, got number)"); 48 | expect(function() 49 | crypto.password.hash("blank", { 50 | algorithm = "argon2b", 51 | }) 52 | end).toThrow("invalid algorithm kind"); 53 | expect(function() 54 | crypto.password.hash("blank", { 55 | algorithm = "argon2d", 56 | time_cost = "abc", 57 | }) 58 | end).toThrow("invalid field 'time_cost' (number expected, got string)"); 59 | expect(function() 60 | crypto.password.hash("blank", { 61 | algorithm = "bcrypt", 62 | cost = "abc", 63 | }) 64 | end).toThrow("invalid field 'cost' (number expected, got string)"); 65 | end) 66 | end) 67 | 68 | return nil; -------------------------------------------------------------------------------- /test/standard/crypto/random.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local crypto = zune.crypto; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Random", function() 10 | test("NextInteger", function() 11 | expect(crypto.random.nextInteger()).toBe(expect.type("number")); 12 | expect(crypto.random.nextInteger(-10, 10)).toBe(expect.type("number")); 13 | expect(function() 14 | crypto.random.nextInteger(-10); 15 | end).toThrow("missing argument #2 to 'nextInteger' (number expected)"); 16 | expect(function() 17 | crypto.random.nextInteger(100, -100); 18 | end).toThrow("invalid range (min > max)"); 19 | expect(crypto.random.nextInteger(0, 0)).toBe(0); 20 | end) 21 | 22 | test("NextNumber", function() 23 | expect(crypto.random.nextNumber()).toBe(expect.type("number")); 24 | expect(crypto.random.nextNumber(-0.5, 0.5)).toBe(expect.type("number")); 25 | expect(function() 26 | crypto.random.nextNumber(-10); 27 | end).toThrow("missing argument #2 to 'nextNumber' (number expected)"); 28 | expect(function() 29 | crypto.random.nextNumber(100, -100); 30 | end).toThrow("invalid range (min > max)"); 31 | expect(crypto.random.nextNumber(0, 0)).toBe(0); 32 | end) 33 | 34 | test("NextBoolean", function() 35 | expect(crypto.random.nextBoolean()).toBe(expect.type("boolean")); 36 | end) 37 | 38 | test("Fill", function() 39 | local buf = buffer.create(16); 40 | 41 | crypto.random.fill(buf, 8, 8); 42 | 43 | expect(buffer.readstring(buf, 0, 8)).toBe("\0\0\0\0\0\0\0\0"); 44 | expect(buffer.readstring(buf, 8, 8)).never.toBe("\0\0\0\0\0\0\0\0"); 45 | 46 | expect(function() 47 | crypto.random.fill(buf, 0, 17); 48 | end).toThrow("invalid length (offset + length > buffer size)"); 49 | expect(function() 50 | crypto.random.fill(buf, 8, 9); 51 | end).toThrow("invalid length (offset + length > buffer size)"); 52 | expect(function() 53 | crypto.random.fill(buf, -1, 1); 54 | end).toThrow("invalid offset (offset < 0)"); 55 | expect(function() 56 | crypto.random.fill(buf, 0, -1); 57 | end).toThrow("invalid length (length < 0)"); 58 | end) 59 | end) 60 | 61 | return nil; -------------------------------------------------------------------------------- /test/standard/testing.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local testing = zune.testing; 3 | 4 | local describe = testing.describe; 5 | local expect = testing.expect; 6 | local test = testing.test; 7 | 8 | assert(type(describe) == "function", `Should be function, got {type(describe)}`); 9 | assert(type(expect) == "table", `Should be table, got {type(expect)}`); 10 | assert(type(test) == "function", `Should be function, got {type(test)}`); 11 | 12 | describe("Test Scope", function() 13 | test("test", function() 14 | end) 15 | test("long", function() 16 | local start = os.clock() + 0.05; 17 | while start > os.clock() do end 18 | end) 19 | test("test error", function() 20 | local ok, err = pcall(error, "err"); 21 | expect(ok).toBe(false); 22 | expect(err).toBe("err"); 23 | end) 24 | test("test case", function() 25 | local ok, err = pcall(function() 26 | expect(1).toBe(2); 27 | end) 28 | expect(ok).toBe(false); 29 | expect(err:find("testing.test.luau:26: Expected", 1)).toBeGreaterThan(1); 30 | end) 31 | test("test failed", function() 32 | expect(1).never.toBe(1); 33 | end) 34 | describe("SubScope", function() 35 | test("test", function() 36 | end) 37 | test("long", function() 38 | local start = os.clock() + 0.05; 39 | while start > os.clock() do end 40 | end) 41 | test("test error", function() 42 | local ok, err = pcall(error, "err"); 43 | expect(ok).toBe(false); 44 | expect(err).toBe("err"); 45 | end) 46 | test("test case", function() 47 | local ok, err = pcall(function() 48 | expect(1).toBe(2); 49 | end) 50 | expect(ok).toBe(false); 51 | expect(err:find("testing.test.luau:48: Expected", 1)).toBeGreaterThan(1); 52 | end) 53 | test("test failed", function() 54 | expect(1).never.toBe(1); 55 | end) 56 | end) 57 | 58 | test("test stacktrace", function() 59 | getfenv(); 60 | local function test() 61 | local function test2() 62 | local function test3() 63 | error("err"); 64 | end 65 | test3(); 66 | end 67 | test2(); 68 | end 69 | test(); 70 | end) 71 | end) 72 | 73 | assert(testing.running, `Should be true, got {testing.running}`); 74 | assert(type(testing._start) == "number", `Should be number, got {testing._start}`); 75 | -------------------------------------------------------------------------------- /legacy/compress/gzip.zig: -------------------------------------------------------------------------------- 1 | const deflate = @import("flate/deflate.zig"); 2 | const inflate = @import("flate/inflate.zig"); 3 | 4 | /// Decompress compressed data from reader and write plain data to the writer. 5 | pub fn decompress(reader: anytype, writer: anytype) !void { 6 | try inflate.decompress(.gzip, reader, writer); 7 | } 8 | 9 | /// Decompressor type 10 | pub fn Decompressor(comptime ReaderType: type) type { 11 | return inflate.Decompressor(.gzip, ReaderType); 12 | } 13 | 14 | /// Create Decompressor which will read compressed data from reader. 15 | pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) { 16 | return inflate.decompressor(.gzip, reader); 17 | } 18 | 19 | /// Compression level, trades between speed and compression size. 20 | pub const Options = deflate.Options; 21 | 22 | /// Compress plain data from reader and write compressed data to the writer. 23 | pub fn compress(reader: anytype, writer: anytype, options: Options) !void { 24 | try deflate.compress(.gzip, reader, writer, options); 25 | } 26 | 27 | /// Compressor type 28 | pub fn Compressor(comptime WriterType: type) type { 29 | return deflate.Compressor(.gzip, WriterType); 30 | } 31 | 32 | /// Create Compressor which outputs compressed data to the writer. 33 | pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) { 34 | return try deflate.compressor(.gzip, writer, options); 35 | } 36 | 37 | /// Huffman only compression. Without Lempel-Ziv match searching. Faster 38 | /// compression, less memory requirements but bigger compressed sizes. 39 | pub const huffman = struct { 40 | pub fn compress(reader: anytype, writer: anytype) !void { 41 | try deflate.huffman.compress(.gzip, reader, writer); 42 | } 43 | 44 | pub fn Compressor(comptime WriterType: type) type { 45 | return deflate.huffman.Compressor(.gzip, WriterType); 46 | } 47 | 48 | pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) { 49 | return deflate.huffman.compressor(.gzip, writer); 50 | } 51 | }; 52 | 53 | // No compression store only. Compressed size is slightly bigger than plain. 54 | pub const store = struct { 55 | pub fn compress(reader: anytype, writer: anytype) !void { 56 | try deflate.store.compress(.gzip, reader, writer); 57 | } 58 | 59 | pub fn Compressor(comptime WriterType: type) type { 60 | return deflate.store.Compressor(.gzip, WriterType); 61 | } 62 | 63 | pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) { 64 | return deflate.store.compressor(.gzip, writer); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /test/standard/serde/init.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local expect = testing.expect; 6 | 7 | expect(serde.json).toBe(expect.similar({ 8 | encode = expect.type("function"), 9 | decode = expect.type("function"), 10 | indents = expect.similar({ 11 | none = expect.type("number"), 12 | two_spaces = expect.type("number"), 13 | four_spaces = expect.type("number"), 14 | tabs = expect.type("number"), 15 | }), 16 | values = expect.similar({ 17 | null = expect.type("table"), 18 | }), 19 | })); 20 | expect(serde.json5).toBe(expect.similar({ 21 | encode = expect.type("function"), 22 | decode = expect.type("function"), 23 | indents = expect.similar({ 24 | none = expect.type("number"), 25 | two_spaces = expect.type("number"), 26 | four_spaces = expect.type("number"), 27 | tabs = expect.type("number"), 28 | }), 29 | values = expect.similar({ 30 | null = expect.type("table"), 31 | }), 32 | })); 33 | expect(serde.json5.values).toBe(serde.json.values); 34 | expect(serde.json5.indents).toBe(serde.json.indents); 35 | expect(serde.toml).toBe(expect.similar({ 36 | encode = expect.type("function"), 37 | decode = expect.type("function"), 38 | })); 39 | expect(serde.yaml).toBe(expect.similar({ 40 | encode = expect.type("function"), 41 | decode = expect.type("function"), 42 | })); 43 | expect(serde.base64).toBe(expect.similar({ 44 | encode = expect.type("function"), 45 | decode = expect.type("function"), 46 | })); 47 | expect(serde.gzip).toBe(expect.similar({ 48 | compress = expect.type("function"), 49 | decompress = expect.type("function"), 50 | })); 51 | expect(serde.zlib).toBe(expect.similar({ 52 | compress = expect.type("function"), 53 | decompress = expect.type("function"), 54 | })); 55 | expect(serde.lz4).toBe(expect.similar({ 56 | compress = expect.type("function"), 57 | compressFrame = expect.type("function"), 58 | decompress = expect.type("function"), 59 | decompressFrame = expect.type("function"), 60 | })); 61 | expect(serde.zstd).toBe(expect.similar({ 62 | compress = expect.type("function"), 63 | decompress = expect.type("function"), 64 | })); 65 | expect(serde.brotli).toBe(expect.similar({ 66 | compress = expect.type("function"), 67 | decompress = expect.type("function"), 68 | })); 69 | 70 | require("@self/json.test"); 71 | require("@self/json5.test"); 72 | require("@self/toml.test"); 73 | require("@self/yaml.test"); 74 | require("@self/base64.test"); 75 | require("@self/gzip.test"); 76 | require("@self/zlib.test"); 77 | require("@self/lz4.test"); 78 | require("@self/zstd.test"); 79 | require("@self/brotli.test"); 80 | -------------------------------------------------------------------------------- /src/core/utils/method_map.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const VM = luau.VM; 5 | 6 | pub fn WithFn( 7 | comptime T: type, 8 | comptime f: anytype, 9 | comptime before: ?fn (ptr: *T, L: *VM.lua.State) anyerror!void, 10 | ) fn (ptr: *T, L: *VM.lua.State) anyerror!i32 { 11 | return struct { 12 | fn inner(ptr: *T, L: *VM.lua.State) !i32 { 13 | if (comptime before) |@"fn"| 14 | try @call(.always_inline, @"fn", .{ ptr, L }); 15 | return switch (@typeInfo(@TypeOf(f))) { 16 | .@"fn" => @call(.always_inline, f, .{ ptr, L }), 17 | else => @compileError("Invalid type for method map"), 18 | }; 19 | } 20 | }.inner; 21 | } 22 | 23 | pub fn CreateNamecallMap( 24 | comptime T: type, 25 | comptime tag: ?i32, 26 | comptime method_map: anytype, 27 | ) fn (L: *VM.lua.State) anyerror!i32 { 28 | const map = std.StaticStringMap(*const fn (ptr: *T, L: *VM.lua.State) anyerror!i32).initComptime(method_map); 29 | 30 | return struct { 31 | fn inner(L: *VM.lua.State) !i32 { 32 | const ptr = if (comptime tag) |t| 33 | L.touserdatatagged(T, 1, t) orelse return L.Zerrorf("invalid userdata", .{}) 34 | else 35 | L.touserdata(T, 1) orelse return L.Zerrorf("invalid userdata", .{}); 36 | 37 | const namecall = L.namecallstr() orelse return 0; 38 | const method = map.get(namecall) orelse return L.Zerrorf("unknown method: {s}", .{namecall}); 39 | return @call(.auto, method, .{ ptr, L }); 40 | } 41 | }.inner; 42 | } 43 | 44 | pub fn CreateStaticIndexMap( 45 | comptime T: type, 46 | comptime tag: i32, 47 | comptime index_map: anytype, 48 | ) fn (L: *VM.lua.State, comptime idx: i32) anyerror!void { 49 | return struct { 50 | fn inner(state: *VM.lua.State, comptime index: i32) !void { 51 | const idx = comptime if (index != VM.lua.GLOBALSINDEX and index != VM.lua.REGISTRYINDEX and index < 0) index - 1 else index; 52 | try state.createtable(0, index_map.len); 53 | inline for (index_map) |kv| { 54 | try state.Zpushfunction(struct { 55 | fn inner(L: *VM.lua.State) !i32 { 56 | const ptr = L.touserdatatagged(T, 1, tag) orelse return L.Zerror("expected ':' calling member function " ++ kv[0]); 57 | return @call(.always_inline, kv[1], .{ ptr, L }); 58 | } 59 | }.inner, kv[0]); 60 | try state.rawsetfield(-2, kv[0]); 61 | } 62 | try state.rawsetfield(idx, "__index"); 63 | } 64 | }.inner; 65 | } 66 | -------------------------------------------------------------------------------- /src/core/standard/serde/gzip.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const lcompress = @import("lcompress"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | const OldWriter = @import("../../utils/old_writer.zig"); 10 | 11 | pub fn lua_compress(L: *VM.lua.State) !i32 { 12 | const allocator = luau.getallocator(L); 13 | 14 | const is_buffer = L.typeOf(1) == .Buffer; 15 | 16 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 17 | 18 | var level: u4 = 12; 19 | 20 | if (!L.typeOf(2).isnoneornil()) { 21 | try L.Zchecktype(2, .Table); 22 | const levelType = L.rawgetfield(2, "level"); 23 | if (!levelType.isnoneornil()) { 24 | if (levelType != .Number) 25 | return L.Zerror("options 'level' field must be a number"); 26 | const num = L.tointeger(-1) orelse unreachable; 27 | if (num < 4 or num > 13) 28 | return L.Zerror("options 'level' must not be over 13 or less than 4 or equal to 10"); 29 | if (num == 10) 30 | return L.Zerrorf("options 'level' cannot be {d}, level does not exist", .{num}); 31 | level = @intCast(num); 32 | } 33 | L.pop(1); 34 | } 35 | 36 | var allocating: std.Io.Writer.Allocating = .init(allocator); 37 | defer allocating.deinit(); 38 | 39 | var stream: std.Io.Reader = .fixed(string); 40 | 41 | try lcompress.gzip.compress(stream.adaptToOldInterface(), OldWriter.adaptToOldInterface(&allocating.writer), .{ 42 | .level = @enumFromInt(level), 43 | }); 44 | 45 | if (is_buffer) 46 | try L.Zpushbuffer(allocating.written()) 47 | else 48 | try L.pushlstring(allocating.written()); 49 | 50 | return 1; 51 | } 52 | 53 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 54 | const allocator = luau.getallocator(L); 55 | 56 | const is_buffer = L.typeOf(1) == .Buffer; 57 | const string = if (is_buffer) 58 | L.Lcheckbuffer(1) 59 | else 60 | L.Lcheckstring(1); 61 | 62 | var allocating: std.Io.Writer.Allocating = .init(allocator); 63 | defer allocating.deinit(); 64 | 65 | var stream: std.Io.Reader = .fixed(string); 66 | 67 | try lcompress.gzip.decompress( 68 | stream.adaptToOldInterface(), 69 | OldWriter.adaptToOldInterface(&allocating.writer), 70 | ); 71 | 72 | const written = allocating.written(); 73 | 74 | if (written.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 75 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 76 | 77 | if (is_buffer) 78 | try L.Zpushbuffer(written) 79 | else 80 | try L.pushlstring(written); 81 | 82 | return 1; 83 | } 84 | -------------------------------------------------------------------------------- /src/core/standard/serde/flate.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const lcompress = @import("lcompress"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | const OldWriter = @import("../../utils/old_writer.zig"); 10 | 11 | pub fn lua_compress(L: *VM.lua.State) !i32 { 12 | const allocator = luau.getallocator(L); 13 | 14 | const is_buffer = L.typeOf(1) == .Buffer; 15 | 16 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 17 | 18 | var level: u4 = 12; 19 | 20 | if (!L.typeOf(2).isnoneornil()) { 21 | try L.Zchecktype(2, .Table); 22 | const levelType = L.rawgetfield(2, "level"); 23 | if (!levelType.isnoneornil()) { 24 | if (levelType != .Number) 25 | return L.Zerror("options 'level' field must be a number"); 26 | const num = L.tointeger(-1) orelse unreachable; 27 | if (num < 4 or num > 13) 28 | return L.Zerror("options 'level' must not be over 13 or less than 4 or equal to 10"); 29 | if (num == 10) 30 | return L.Zerrorf("options 'level' cannot be {d}, level does not exist", .{num}); 31 | level = @intCast(num); 32 | } 33 | L.pop(1); 34 | } 35 | 36 | var allocating: std.Io.Writer.Allocating = .init(allocator); 37 | defer allocating.deinit(); 38 | 39 | var stream: std.Io.Reader = .fixed(string); 40 | 41 | try lcompress.flate.compress(stream.adaptToOldInterface(), OldWriter.adaptToOldInterface(&allocating.writer), .{ 42 | .level = @enumFromInt(level), 43 | }); 44 | 45 | if (is_buffer) 46 | try L.Zpushbuffer(allocating.written()) 47 | else 48 | try L.pushlstring(allocating.written()); 49 | 50 | return 1; 51 | } 52 | 53 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 54 | const allocator = luau.getallocator(L); 55 | 56 | const is_buffer = L.typeOf(1) == .Buffer; 57 | const string = if (is_buffer) 58 | L.Lcheckbuffer(1) 59 | else 60 | L.Lcheckstring(1); 61 | 62 | var allocating: std.Io.Writer.Allocating = .init(allocator); 63 | defer allocating.deinit(); 64 | 65 | var stream: std.Io.Reader = .fixed(string); 66 | 67 | try lcompress.flate.decompress( 68 | stream.adaptToOldInterface(), 69 | OldWriter.adaptToOldInterface(&allocating.writer), 70 | ); 71 | 72 | const written = allocating.written(); 73 | 74 | if (written.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 75 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 76 | 77 | if (is_buffer) 78 | try L.Zpushbuffer(written) 79 | else 80 | try L.pushlstring(written); 81 | 82 | return 1; 83 | } 84 | -------------------------------------------------------------------------------- /src/core/utils/sysfd.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | pub const context = switch (builtin.os.tag) { 5 | .windows => struct { 6 | pub const POLLIN: i16 = 0x0100; 7 | pub const POLLERR: i16 = 0x0001; 8 | pub const POLLHUP: i16 = 0x0002; 9 | pub const POLLNVAL: i16 = 0x0004; 10 | pub const INVALID_SOCKET = std.os.windows.ws2_32.INVALID_SOCKET; 11 | pub const pollfd = struct { 12 | fd: std.os.windows.HANDLE, 13 | events: std.os.windows.SHORT, 14 | revents: std.os.windows.SHORT, 15 | }; 16 | pub const spollfd = std.os.windows.ws2_32.pollfd; 17 | pub fn spoll(fds: []spollfd, timeout: i32) !usize { 18 | const rc = std.os.windows.poll(fds.ptr, @intCast(fds.len), timeout); 19 | if (rc == std.os.windows.ws2_32.SOCKET_ERROR) { 20 | switch (std.os.windows.ws2_32.WSAGetLastError()) { 21 | .WSAENOBUFS => return error.SystemResources, 22 | .WSAENETDOWN => return error.NetworkSubsystemFailed, 23 | .WSANOTINITIALISED => unreachable, 24 | else => |err| return std.os.windows.unexpectedWSAError(err), 25 | } 26 | } else return @intCast(rc); 27 | } 28 | pub fn poll(fds: []pollfd, timeout: i32) !usize { 29 | var handles: [256]std.os.windows.HANDLE = undefined; 30 | if (fds.len > handles.len) 31 | return error.TooManyHandles; 32 | for (fds, 0..) |fd, i| handles[i] = fd.fd; 33 | 34 | const res = std.os.windows.kernel32.WaitForMultipleObjects(@intCast(fds.len), &handles, std.os.windows.FALSE, @intCast(timeout)); 35 | 36 | if (res == std.os.windows.WAIT_TIMEOUT) 37 | return 0; 38 | 39 | if (res >= std.os.windows.WAIT_OBJECT_0 and @as(usize, @intCast(res)) < std.os.windows.WAIT_OBJECT_0 + fds.len) { 40 | const index = res - std.os.windows.WAIT_OBJECT_0; 41 | fds[index].revents = fds[index].events; 42 | return 1; 43 | } 44 | 45 | return error.UnexpectedWaitResult; 46 | } 47 | }, 48 | .macos, .linux, .freebsd => struct { 49 | pub const POLLIN: i16 = 0x0001; 50 | pub const POLLERR: i16 = 0x0008; 51 | pub const POLLHUP: i16 = 0x0010; 52 | pub const POLLNVAL: i16 = 0x0020; 53 | pub const INVALID_SOCKET = -1; 54 | pub const pollfd = std.posix.pollfd; 55 | pub const spollfd = std.posix.pollfd; 56 | pub const poll = std.posix.poll; 57 | pub const spoll = std.posix.poll; 58 | }, 59 | else => @compileError("Unsupported OS"), 60 | }; 61 | -------------------------------------------------------------------------------- /src/core/standard/serde/zlib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const lcompress = @import("lcompress"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | const OldWriter = @import("../../utils/old_writer.zig"); 10 | 11 | pub fn lua_compress(L: *VM.lua.State) !i32 { 12 | const allocator = luau.getallocator(L); 13 | 14 | const is_buffer = L.typeOf(1) == .Buffer; 15 | 16 | const string = if (is_buffer) 17 | L.Lcheckbuffer(1) 18 | else 19 | L.Lcheckstring(1); 20 | 21 | var level: u4 = 12; 22 | 23 | if (!L.typeOf(2).isnoneornil()) { 24 | try L.Zchecktype(2, .Table); 25 | const levelType = L.rawgetfield(2, "level"); 26 | if (!levelType.isnoneornil()) { 27 | if (levelType != .Number) 28 | return L.Zerror("options 'level' field must be a number"); 29 | const num = L.tointeger(-1) orelse unreachable; 30 | if (num < 4 or num > 13) 31 | return L.Zerror("options 'level' must not be over 13 or less than 4 or equal to 10"); 32 | if (num == 10) 33 | return L.Zerrorf("options 'level' cannot be {d}, level does not exist", .{num}); 34 | level = @intCast(num); 35 | } 36 | L.pop(1); 37 | } 38 | 39 | var allocating: std.Io.Writer.Allocating = .init(allocator); 40 | defer allocating.deinit(); 41 | 42 | var stream: std.Io.Reader = .fixed(string); 43 | 44 | try lcompress.zlib.compress(stream.adaptToOldInterface(), OldWriter.adaptToOldInterface(&allocating.writer), .{ 45 | .level = @enumFromInt(level), 46 | }); 47 | 48 | if (is_buffer) 49 | try L.Zpushbuffer(allocating.written()) 50 | else 51 | try L.pushlstring(allocating.written()); 52 | 53 | return 1; 54 | } 55 | 56 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 57 | const allocator = luau.getallocator(L); 58 | 59 | const is_buffer = L.typeOf(1) == .Buffer; 60 | 61 | const string = if (is_buffer) 62 | L.Lcheckbuffer(1) 63 | else 64 | L.Lcheckstring(1); 65 | 66 | var allocating: std.Io.Writer.Allocating = .init(allocator); 67 | defer allocating.deinit(); 68 | 69 | var stream: std.Io.Reader = .fixed(string); 70 | 71 | try lcompress.zlib.decompress( 72 | stream.adaptToOldInterface(), 73 | OldWriter.adaptToOldInterface(&allocating.writer), 74 | ); 75 | 76 | const written = allocating.written(); 77 | 78 | if (written.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 79 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 80 | 81 | if (is_buffer) 82 | try L.Zpushbuffer(written) 83 | else 84 | try L.pushlstring(written); 85 | 86 | return 1; 87 | } 88 | -------------------------------------------------------------------------------- /test/standard/random.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local random = zune.random; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | local function randomTest(impl: any) 10 | do 11 | local rand: Random = impl.new(); 12 | for i = 1, 20 do 13 | rand:nextInteger(0, 10); 14 | end 15 | for i = 1, 20 do 16 | rand:nextNumber(0, 10); 17 | end 18 | expect(rand:nextBoolean()).toBe(expect.type("boolean")); 19 | end 20 | 21 | local rand: Random = impl.new(0); 22 | for i = 1, 20 do 23 | local result = rand:nextInteger(0, 10); 24 | expect(result).toBeGreaterThanOrEqual(0); 25 | expect(result).toBeLessThanOrEqual(10); 26 | expect(result // 1).toBe(result); 27 | end 28 | for i = 1, 20 do 29 | local result = rand:nextNumber(0, 10); 30 | expect(result).toBeGreaterThanOrEqual(0); 31 | expect(result).toBeLessThanOrEqual(10); 32 | expect(result // 1).never.toBe(result); 33 | end 34 | 35 | expect(rand:nextNumber(0, math.huge)).toBe(math.huge); 36 | expect(rand:nextNumber(math.huge, 0)).toBe(math.huge); 37 | expect(rand:nextNumber(math.huge, math.huge)).toBe(math.huge); 38 | expect(rand:nextNumber(0, -math.huge)).toBe(-math.huge); 39 | expect(rand:nextNumber(-math.huge, 0)).toBe(-math.huge); 40 | expect(rand:nextNumber(-math.huge, -math.huge)).toBe(-math.huge); 41 | expect(tostring(rand:nextNumber(-math.huge, math.huge))).toBe(tostring(0/0)); 42 | expect(tostring(rand:nextNumber(math.huge, -math.huge))).toBe(tostring(0/0)); 43 | expect(tostring(rand:nextNumber(0/0, 0/0))).toBe(tostring(0/0)); 44 | expect(tostring(rand:nextNumber(0, 0/0))).toBe(tostring(0/0)); 45 | expect(tostring(rand:nextNumber(0/0, 0))).toBe(tostring(0/0)); 46 | 47 | local copy = rand:clone(); 48 | expect(copy:nextInteger(0, 10)).toBe(rand:nextInteger(0, 10)); 49 | copy:nextInteger(0, 10); 50 | copy:nextInteger(0, 10); 51 | expect(copy:nextInteger(0, 10)).never.toBe(rand:nextInteger(0, 10)); 52 | end 53 | 54 | describe("Random", function() 55 | test("Default", function() 56 | randomTest(random); 57 | end) 58 | test("LuauPcg32", function() 59 | randomTest(random.LuauPcg32); 60 | end) 61 | test("Isaac64", function() 62 | randomTest(random.Isaac64); 63 | end) 64 | test("Pcg32", function() 65 | randomTest(random.Pcg32); 66 | end) 67 | test("Xoroshiro128", function() 68 | randomTest(random.Xoroshiro128); 69 | end) 70 | test("Xoshiro256", function() 71 | randomTest(random.Xoshiro256); 72 | end) 73 | test("Sfc64", function() 74 | randomTest(random.Sfc64); 75 | end) 76 | test("RomuTrio", function() 77 | randomTest(random.RomuTrio); 78 | end) 79 | end) 80 | -------------------------------------------------------------------------------- /test/standard/ffi/sample.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | export fn blank() void { 4 | // This function is intentionally left blank. 5 | } 6 | 7 | export fn add(a: i32, b: i32) i32 { 8 | return a + b; 9 | } 10 | 11 | export fn add_float(a: f64, b: f64) f64 { 12 | return a + b; 13 | } 14 | 15 | export fn add_ptr(a_ptr: *i32, b: i32) void { 16 | a_ptr.* += b; 17 | } 18 | 19 | export fn add_ptr_ptr(a_ptr_ptr: **i32, b: i32) void { 20 | a_ptr_ptr.*.* += b; 21 | } 22 | 23 | export fn fire_callback(callback: *const fn (i32) callconv(.c) i8) bool { 24 | return callback(123) == -1; 25 | } 26 | 27 | fn the_callback(a: i32) callconv(.c) i32 { 28 | return a + 2555; 29 | } 30 | 31 | export fn double_call(callback: *const fn (*const fn (i32) callconv(.c) i32) callconv(.c) i8) bool { 32 | return callback(the_callback) == 1; 33 | } 34 | 35 | export fn check_string(string: [*c]const u8) bool { 36 | return std.mem.eql(u8, std.mem.span(string), "hello"); 37 | } 38 | 39 | export fn check_nullptr(ptr: [*c]u8) bool { 40 | return ptr == null; 41 | } 42 | 43 | const Foo = extern struct { 44 | x: i32, 45 | y: i32, 46 | }; 47 | 48 | export fn check_struct(foo: Foo) bool { 49 | return foo.x == 1 and foo.y == 2; 50 | } 51 | 52 | const Foo2 = extern struct { 53 | x: f64, 54 | y: f64, 55 | }; 56 | 57 | export fn check_struct2(foo: Foo2) bool { 58 | return foo.x == 1.1 and foo.y == 2.2; 59 | } 60 | 61 | const Foo3 = extern struct { 62 | x: f64, 63 | }; 64 | 65 | export fn check_struct3(foo: Foo3) bool { 66 | return foo.x == 1.1; 67 | } 68 | 69 | const Vec2_32 = extern struct { 70 | x: f32, 71 | y: f32, 72 | }; 73 | 74 | export fn check_vec2_32(foo: Vec2_32) bool { 75 | return foo.x == 1.23 and foo.y == 2.34; 76 | } 77 | 78 | const Vec3_32 = extern struct { 79 | x: f32, 80 | y: f32, 81 | z: f32, 82 | }; 83 | 84 | export fn check_vec3_32(foo: Vec3_32) bool { 85 | return foo.x == 1.23 and foo.y == 2.34 and foo.z == 3.45; 86 | } 87 | 88 | export fn new_vec2_32(x: f32, y: f32) Vec2_32 { 89 | return .{ 90 | .x = x, 91 | .y = y, 92 | }; 93 | } 94 | 95 | export fn new_vec3_32(x: f32, y: f32, z: f32) Vec3_32 { 96 | return .{ 97 | .x = x, 98 | .y = y, 99 | .z = z, 100 | }; 101 | } 102 | 103 | const SmallField = extern struct { 104 | first: u32, 105 | second: *allowzero anyopaque, 106 | }; 107 | 108 | export fn get_small_field() SmallField { 109 | return .{ 110 | .first = 67, 111 | .second = @ptrFromInt(0), 112 | }; 113 | } 114 | 115 | export fn new_i32() *i32 { 116 | const ptr = std.heap.page_allocator.create(i32) catch @panic("allocation failed"); 117 | ptr.* = 123; 118 | return ptr; 119 | } 120 | export fn free_i32(ptr: *i32) void { 121 | std.heap.page_allocator.destroy(ptr); 122 | } 123 | 124 | test add { 125 | const result = add(1, 2); 126 | try std.testing.expect(result == 3); 127 | } 128 | -------------------------------------------------------------------------------- /src/commands/luau.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const Zune = @import("zune"); 5 | 6 | const command = @import("lib.zig"); 7 | 8 | const USAGE = "usage: luau \n"; 9 | fn Execute(allocator: std.mem.Allocator, args: []const []const u8) !void { 10 | if (args.len != 1) { 11 | return std.debug.print(USAGE, .{}); 12 | } 13 | 14 | if (std.mem.eql(u8, args[0], "list-fflags")) { 15 | var long_name: usize = 0; 16 | 17 | const bool_flags = luau.FFlags.Get(bool); 18 | const int_flags = luau.FFlags.Get(i32); 19 | 20 | { 21 | var iter = bool_flags.iterator(); 22 | while (iter.next()) |flag| 23 | long_name = @max(long_name, std.mem.span(flag.name).len); 24 | var iter2 = int_flags.iterator(); 25 | while (iter2.next()) |flag| 26 | long_name = @max(long_name, std.mem.span(flag.name).len); 27 | } 28 | 29 | { 30 | inline for (&[_]type{ bool, i32 }) |t| { 31 | const flags = luau.FFlags.Get(t); 32 | var iter = flags.iterator(); 33 | while (iter.next()) |flag| { 34 | const name = std.mem.span(flag.name); 35 | const pad_size = (long_name - name.len) + 2; 36 | 37 | var padding = try allocator.alloc(u8, pad_size); 38 | defer allocator.free(padding); 39 | 40 | for (0..pad_size) |i| 41 | padding[i] = '.'; 42 | 43 | if (t == bool) { 44 | if (flag.value) Zune.debug.print( 45 | "{s} {s} = (bool) true\n", 46 | .{ 47 | name, 48 | padding, 49 | }, 50 | ) else Zune.debug.print( 51 | "{s} {s} = (bool) false\n", 52 | .{ 53 | name, 54 | padding, 55 | }, 56 | ); 57 | } else { 58 | Zune.debug.print( 59 | "{s} {s} = (int) {d}\n", 60 | .{ 61 | name, 62 | padding, 63 | flag.value, 64 | }, 65 | ); 66 | } 67 | } 68 | } 69 | } 70 | } else if (std.mem.eql(u8, args[0], "version")) { 71 | std.debug.print("{}.{}\n", .{ luau.LUAU_VERSION.major, luau.LUAU_VERSION.minor }); 72 | } else { 73 | return std.debug.print(USAGE, .{}); 74 | } 75 | } 76 | 77 | pub const Command = command.Command{ 78 | .name = "luau", 79 | .execute = Execute, 80 | }; 81 | -------------------------------------------------------------------------------- /src/core/utils/testrunner.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const xev = @import("xev"); 3 | const luau = @import("luau"); 4 | const builtin = @import("builtin"); 5 | 6 | const Zune = @import("zune"); 7 | 8 | const Engine = Zune.Runtime.Engine; 9 | const Scheduler = Zune.Runtime.Scheduler; 10 | 11 | const VM = luau.VM; 12 | 13 | const zune_test_files = @import("zune-test-files"); 14 | 15 | const TestFile = struct { 16 | path: []const u8, 17 | }; 18 | 19 | pub fn newTestFile(comptime path: []const u8) TestFile { 20 | return TestFile{ 21 | .path = "test/" ++ path, 22 | }; 23 | } 24 | 25 | const TestOptions = struct { 26 | std_out: bool = true, 27 | ref_leak_check: bool = true, 28 | }; 29 | 30 | pub fn runTest(comptime testFile: TestFile, args: []const []const u8, comptime options: TestOptions) !Zune.corelib.testing.TestResult { 31 | const allocator = std.testing.allocator; 32 | 33 | try Zune.init(); 34 | 35 | var L = try luau.init(&allocator); 36 | defer L.deinit(); 37 | 38 | if (!options.std_out) 39 | try L.Zsetfield(VM.lua.GLOBALSINDEX, "_testing_stdOut", false); 40 | 41 | var scheduler = try Scheduler.init(allocator, L); 42 | defer scheduler.deinit(); 43 | 44 | try Zune.initState(L); 45 | defer Zune.deinitState(L); 46 | 47 | var temporaryDir = std.testing.tmpDir(.{ 48 | .access_sub_paths = true, 49 | }); 50 | // FIXME: freezes windows 51 | // defer temporaryDir.cleanup(); 52 | 53 | const tempPath = try std.mem.joinZ(allocator, "/", &.{ 54 | ".zig-cache/tmp", 55 | &temporaryDir.sub_path, 56 | }); 57 | defer allocator.free(tempPath); 58 | try L.Zsetglobal("__test_tempdir", tempPath); 59 | 60 | const cwd = std.fs.cwd(); 61 | 62 | const content = try cwd.readFileAlloc(allocator, testFile.path, std.math.maxInt(usize)); 63 | defer allocator.free(content); 64 | 65 | const dir_path = std.fs.path.dirname(testFile.path) orelse unreachable; 66 | var dir = try cwd.openDir(dir_path, .{}); 67 | defer dir.close(); 68 | 69 | Zune.STATE.RUN_MODE = .Test; 70 | 71 | Zune.loadConfiguration(dir); 72 | 73 | const current_top = L.gettop(); 74 | try Engine.prepAsync(L, &scheduler); 75 | try Zune.openZune(L, args, .{}); 76 | 77 | L.setsafeenv(VM.lua.GLOBALSINDEX, true); 78 | std.debug.assert(L.gettop() == current_top); // zune should not leave anything on the stack 79 | 80 | const ML = try L.newthread(); 81 | 82 | try ML.Lsandboxthread(); 83 | 84 | try Engine.setLuaFileContext(ML, .{ 85 | .main = true, 86 | }); 87 | 88 | ML.setsafeenv(VM.lua.GLOBALSINDEX, true); 89 | 90 | Engine.loadModule(ML, "@" ++ testFile.path, content, .{ 91 | .debugLevel = 2, 92 | }) catch |err| switch (err) { 93 | error.Syntax => { 94 | std.debug.print("Syntax: {s}\n", .{ML.tostring(-1) orelse "UnknownError"}); 95 | return err; 96 | }, 97 | else => return err, 98 | }; 99 | 100 | Zune.corelib.testing.REF_LEAK_CHECK = options.ref_leak_check; 101 | return Zune.corelib.testing.runTestAsync(ML, &scheduler) catch |err| return err; 102 | } 103 | -------------------------------------------------------------------------------- /test/standard/serde/zlib.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Zlib", function() 10 | local sample = string.rep([[ 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 12 | ]], 20); 13 | local compressed_sample_default = "x\x9C\xED\xCC\xC1\r\xC4 \f\x04\xC0\xFFU\xB1\x05\x9CRI\x9A@`E+\x01F\xB6\xE9?\x8F|\x92\x1E<\x05\f\xF08\xD5d\x80\xCB\xF7@\xD3\xAE\x06g\xA0\f\x89?\xAAN\x97\x1A\x12\xDBP\x1A\x17\xBDr^\x90\xCE8~x\xC9$\x93L2\xC9\xE4\x93\xDC\x93\xEC\xC2\x10"; 14 | local compressed_sample_fast = "x\x9C\xED\xCC\xB1\r\xC5 \fE\xD1\xFEO\xF1\x06\xF8\xCA$Y\x02\x81\x15=\t0\xB2\xCD\xFE)\x92.+\xF8\xF6\xF7\x00O\xA7\x9A\fp\xF9\x1Eh\xDA\xD5\xE0\f\x94!\xF1G\xD5\xE9RCb\x1BJ\xE3\xA2W\xCE\v\xD2\x19\xC7\xEF\xFD\x13I$\x91D\x12\xF9\"7\x93\xEC\xC2\x10"; 15 | 16 | describe("Compression", function() 17 | test("Default", function() 18 | expect(serde.zlib.compress(sample)).toBe(compressed_sample_default); 19 | end) 20 | test("Buffer (Default)", function() 21 | local buf = serde.zlib.compress(buffer.fromstring(sample)); 22 | expect(buf).toBe(expect.type("buffer")); 23 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 24 | end) 25 | test("Level 4", function() 26 | expect(serde.zlib.compress(sample, {level = 4})).toBe(compressed_sample_fast); 27 | end) 28 | test("Level 9", function() 29 | expect(serde.zlib.compress(sample, {level = 9})).toBe(compressed_sample_default); 30 | end) 31 | test("Fast", function() 32 | expect(serde.zlib.compress(sample, {level = 11})).toBe(compressed_sample_fast); 33 | end) 34 | test("Best", function() 35 | expect(serde.zlib.compress(sample, {level = 13})).toBe(compressed_sample_default); 36 | end) 37 | test("Fail", function() 38 | expect(function() serde.zlib.compress(sample, {level = 10}) end).toThrow("options 'level' cannot be 10, level does not exist"); 39 | expect(function() serde.zlib.compress(sample, {level = 3}) end).toThrow("options 'level' must not be over 13 or less than 4 or equal to 10"); 40 | expect(function() serde.zlib.compress(sample, {level = 14}) end).toThrow("options 'level' must not be over 13 or less than 4 or equal to 10"); 41 | expect(function() serde.zlib.compress(true) end).toThrow("invalid argument #1 to 'compress' (string expected, got boolean)"); 42 | end) 43 | end) 44 | 45 | describe("Decompression", function() 46 | test("Default", function() 47 | expect(serde.zlib.decompress(compressed_sample_default)).toBe(sample); 48 | end) 49 | test("Buffer (Default)", function() 50 | local buf = serde.zlib.decompress(buffer.fromstring(compressed_sample_default)); 51 | expect(buf).toBe(expect.type("buffer")); 52 | expect(buffer.tostring(buf)).toBe(sample); 53 | end) 54 | test("Fast", function() 55 | expect(serde.zlib.decompress(compressed_sample_fast)).toBe(sample); 56 | end) 57 | end) 58 | end) 59 | 60 | return nil; 61 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Scythe Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | This project includes third-party components with the following licenses: 26 | 27 | | Library | License | 28 | |---------|---------| 29 | | [`zig-luau`](https://github.com/Scythe-Technology/zig-luau) | [MIT](https://github.com/Scythe-Technology/zig-luau/blob/master/license), [MIT](https://github.com/luau-lang/luau/blob/master/LICENSE.txt), [MIT](https://github.com/luau-lang/luau/blob/master/lua_LICENSE.txt) | 30 | | [`zig-json`](https://github.com/Scythe-Technology/zig-json) | [MIT](https://github.com/Scythe-Technology/zig-json/blob/master/LICENSE) | 31 | | [`zig-yaml`](https://github.com/kubkon/zig-yaml) | [MIT](https://github.com/kubkon/zig-yaml/blob/main/LICENSE) | 32 | | [`zig-lz4`](https://github.com/Scythe-Technology/zig-lz4) | [MIT](https://github.com/Scythe-Technology/zig-lz4/blob/master/LICENSE), [BSD 2-Clause](https://github.com/lz4/lz4/blob/dev/LICENSE) | 33 | | [`zpcre2`](https://github.com/Scythe-Technology/zpcre2) | [MIT](https://github.com/Scythe-Technology/zpcre2/blob/master/LICENSE), [BSD 3-Clause](https://github.com/Scythe-Technology/zpcre2/blob/master/PCRE2-LICENCE.md) | 34 | | [`zig-tinycc`](https://github.com/Scythe-Technology/zig-tinycc) | [MIT](https://github.com/Scythe-Technology/zffi/blob/master/LICENSE), [LGPL](https://github.com/TinyCC/tinycc/blob/mob/COPYING) | 35 | | [`zdt`](https://github.com/FObersteiner/zdt) | [MPL-2.0](https://github.com/FObersteiner/zdt/blob/master/LICENSE)| 36 | | [`tomlz`](https://github.com/mattyhall/tomlz) | [MIT](https://github.com/mattyhall/tomlz/blob/main/LICENCE) | 37 | | [`z-sqlite`](https://github.com/Scythe-Technology/z-sqlite) | [MIT](https://github.com/Scythe-Technology/z-sqlite/blob/master/LICENSE), [SQLite](https://sqlite.org/copyright.html) | 38 | | [`zig-zstd`](https://github.com/Scythe-Technology/zig-zstd) | [MIT](https://github.com/Scythe-Technology/zig-zstd/blob/master/LICENSE), [BSD 3-Clause](https://github.com/facebook/zstd/blob/dev/LICENSE) | 39 | | [`libxev`](https://github.com/Scythe-Technology/libxev) | [MIT](https://github.com/Scythe-Technology/libxev/blob/main/LICENSE) | 40 | | [`tls`](https://github.com/ianic/tls.zig) | [MIT](https://github.com/ianic/tls.zig/blob/602f05bd84d819d487a6e22dd1463e907f7992ab/LICENSE) | 41 | -------------------------------------------------------------------------------- /test/standard/serde/gzip.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Gzip", function() 10 | local sample = string.rep([[ 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 12 | ]], 20); 13 | local compressed_sample_default = "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\xED\xCC\xC1\r\xC4 \f\x04\xC0\xFFU\xB1\x05\x9CRI\x9A@`E+\x01F\xB6\xE9?\x8F|\x92\x1E<\x05\f\xF08\xD5d\x80\xCB\xF7@\xD3\xAE\x06g\xA0\f\x89?\xAAN\x97\x1A\x12\xDBP\x1A\x17\xBDr^\x90\xCE8~x\xC9$\x93L2\xC9\xE4\x93\xDC=\x87\xB4fd\x05\x00\x00"; 14 | local compressed_sample_fast = "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\xED\xCC\xB1\r\xC5 \fE\xD1\xFEO\xF1\x06\xF8\xCA$Y\x02\x81\x15=\t0\xB2\xCD\xFE)\x92.+\xF8\xF6\xF7\x00O\xA7\x9A\fp\xF9\x1Eh\xDA\xD5\xE0\f\x94!\xF1G\xD5\xE9RCb\x1BJ\xE3\xA2W\xCE\v\xD2\x19\xC7\xEF\xFD\x13I$\x91D\x12\xF9\"7=\x87\xB4fd\x05\x00\x00"; 15 | 16 | describe("Compression", function() 17 | test("Default", function() 18 | expect(serde.gzip.compress(sample)).toBe(compressed_sample_default); 19 | end) 20 | test("Buffer (Default)", function() 21 | local buf = serde.gzip.compress(buffer.fromstring(sample)); 22 | expect(buf).toBe(expect.type("buffer")); 23 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 24 | end) 25 | test("Level 4", function() 26 | expect(serde.gzip.compress(sample, {level = 4})).toBe(compressed_sample_fast); 27 | end) 28 | test("Level 9", function() 29 | expect(serde.gzip.compress(sample, {level = 9})).toBe(compressed_sample_default); 30 | end) 31 | test("Fast", function() 32 | expect(serde.gzip.compress(sample, {level = 11})).toBe(compressed_sample_fast); 33 | end) 34 | test("Best", function() 35 | expect(serde.gzip.compress(sample, {level = 13})).toBe(compressed_sample_default); 36 | end) 37 | test("Fail", function() 38 | expect(function() serde.gzip.compress(sample, {level = 10}) end).toThrow("options 'level' cannot be 10, level does not exist"); 39 | expect(function() serde.gzip.compress(sample, {level = 3}) end).toThrow("options 'level' must not be over 13 or less than 4 or equal to 10"); 40 | expect(function() serde.gzip.compress(sample, {level = 14}) end).toThrow("options 'level' must not be over 13 or less than 4 or equal to 10"); 41 | expect(function() serde.gzip.compress(true) end).toThrow("invalid argument #1 to 'compress' (string expected, got boolean)"); 42 | end) 43 | end) 44 | 45 | describe("Decompression", function() 46 | test("Default", function() 47 | expect(serde.gzip.decompress(compressed_sample_default)).toBe(sample); 48 | end) 49 | test("Buffer (Default)", function() 50 | local buf = serde.gzip.decompress(buffer.fromstring(compressed_sample_default)); 51 | expect(buf).toBe(expect.type("buffer")); 52 | expect(buffer.tostring(buf)).toBe(sample); 53 | end) 54 | test("Fast", function() 55 | expect(serde.gzip.decompress(compressed_sample_fast)).toBe(sample); 56 | end) 57 | end) 58 | end) 59 | 60 | return nil; 61 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | fmt: 12 | name: Format Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Zig 19 | uses: mlugg/setup-zig@v2 20 | with: 21 | version: 0.15.1 22 | use-cache: false 23 | 24 | - name: Run fmt 25 | run: zig fmt --check **/*.zig 26 | 27 | prep: 28 | name: Prepare 29 | runs-on: ubuntu-latest 30 | outputs: 31 | version: ${{ steps.version.outputs.version }} 32 | steps: 33 | - name: Checkout Repository 34 | uses: actions/checkout@v4 35 | 36 | - name: Setup Zig 37 | uses: mlugg/setup-zig@v2 38 | with: 39 | version: 0.15.2 40 | use-cache: false 41 | 42 | - name: Get Zune Version 43 | id: version 44 | run: | 45 | version=$(zig build version) 46 | echo "version=$version">> $GITHUB_OUTPUT 47 | 48 | test: 49 | needs: [fmt, prep] 50 | strategy: 51 | matrix: 52 | include: 53 | - os: ubuntu-latest 54 | name: Linux x86_64 55 | target: x86_64-linux-gnu 56 | artifact: zune-${{ needs.prep.outputs.version }}-linux-x86_64 57 | - os: windows-latest 58 | name: Windows x86_64 59 | target: x86_64-windows 60 | artifact: zune-${{ needs.prep.outputs.version }}-windows-x86_64 61 | ext: .exe 62 | - os: macos-15-intel 63 | name: macOS x86_64 64 | target: x86_64-macos 65 | llvm: true 66 | artifact: zune-${{ needs.prep.outputs.version }}-macos-x86_64 67 | - os: macos-14 68 | name: macOS Arm64 69 | target: aarch64-macos 70 | artifact: zune-${{ needs.prep.outputs.version }}-macos-aarch64 71 | - os: ubuntu-22.04-arm 72 | name: Linux Arm64 73 | target: aarch64-linux 74 | artifact: zune-${{ needs.prep.outputs.version }}-linux-aarch64 75 | - os: ubuntu-latest 76 | name: Linux Risc-v64 77 | target: riscv64-linux 78 | qemu-platform: riscv64 79 | artifact: zune-${{ needs.prep.outputs.version }}-linux-riscv64 80 | 81 | name: ${{ matrix.name }} 82 | timeout-minutes: 20 83 | runs-on: ${{matrix.os}} 84 | steps: 85 | - name: Checkout Repository 86 | uses: actions/checkout@v4 87 | 88 | - name: Setup Zig 89 | uses: mlugg/setup-zig@v2 90 | with: 91 | version: 0.15.2 92 | use-cache: false 93 | 94 | - name: Setup QEMU 95 | if: matrix.qemu-platform != '' 96 | uses: docker/setup-qemu-action@v1 97 | 98 | - name: Run info 99 | continue-on-error: ${{ matrix.qemu-platform != '' }} 100 | run: zig build run -Dtarget=${{ matrix.target }}${{ matrix.llvm != '' && ' -Dllvm' || '' }}${{ matrix.qemu-platform != '' && ' -fqemu' || '' }} -- info all 101 | 102 | - name: Run tests 103 | continue-on-error: ${{ matrix.qemu-platform != '' }} 104 | run: zig build test -Dtarget=${{ matrix.target }}${{ matrix.llvm != '' && ' -Dllvm' || '' }}${{ matrix.qemu-platform != '' && ' -fqemu' || '' }} --verbose 105 | 106 | - name: Build debug 107 | run: zig build -Dtarget=${{ matrix.target }} --verbose 108 | -------------------------------------------------------------------------------- /src/core/standard/crypto/common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const VM = luau.VM; 5 | 6 | pub fn lua_genEncryptFn(comptime algorithm: anytype) VM.zapi.LuaZigFn(anyerror!i32) { 7 | return struct { 8 | fn encrypt(L: *VM.lua.State) !i32 { 9 | const allocator = luau.getallocator(L); 10 | 11 | const msg = try L.Zcheckvalue([]const u8, 1, null); 12 | const key = try L.Zcheckvalue([]const u8, 2, null); 13 | 14 | if (key.len != algorithm.key_length) 15 | return L.Zerrorf("invalid key length (expected size {d})", .{algorithm.key_length}); 16 | 17 | const nonce = try L.Zcheckvalue([]const u8, 3, null); 18 | 19 | if (nonce.len != algorithm.nonce_length) 20 | return L.Zerrorf("invalid nonce length (expected size {d})", .{algorithm.nonce_length}); 21 | 22 | const ad = try L.Zcheckvalue(?[]const u8, 4, null) orelse ""; 23 | 24 | var tag: [algorithm.tag_length]u8 = undefined; 25 | const c = try allocator.alloc(u8, msg.len); 26 | defer allocator.free(c); 27 | 28 | var snonce: [algorithm.nonce_length]u8 = undefined; 29 | @memcpy(snonce[0..], nonce[0..algorithm.nonce_length]); 30 | var skey: [algorithm.key_length]u8 = undefined; 31 | @memcpy(skey[0..], key[0..algorithm.key_length]); 32 | 33 | algorithm.encrypt(c, &tag, msg, ad, snonce, skey); 34 | 35 | try L.createtable(0, 2); 36 | 37 | try L.Zpushbuffer(c); 38 | try L.rawsetfield(-2, "cipher"); 39 | 40 | try L.Zpushbuffer(&tag); 41 | try L.rawsetfield(-2, "tag"); 42 | 43 | return 1; 44 | } 45 | }.encrypt; 46 | } 47 | 48 | pub fn lua_genDecryptFn(comptime algorithm: anytype) VM.zapi.LuaZigFn(anyerror!i32) { 49 | return struct { 50 | fn decrypt(L: *VM.lua.State) !i32 { 51 | const allocator = luau.getallocator(L); 52 | 53 | const cipher = try L.Zcheckvalue([]const u8, 1, null); 54 | const tag = try L.Zcheckvalue([]const u8, 2, null); 55 | 56 | if (tag.len != algorithm.tag_length) 57 | return L.Zerrorf("invalid tag length (expected size {})", .{algorithm.tag_length}); 58 | 59 | const key = try L.Zcheckvalue([]const u8, 3, null); 60 | 61 | if (key.len != algorithm.key_length) 62 | return L.Zerrorf("invalid key length (expected size {})", .{algorithm.key_length}); 63 | 64 | const nonce = try L.Zcheckvalue([]const u8, 4, null); 65 | 66 | if (nonce.len != algorithm.nonce_length) 67 | return L.Zerrorf("invalid nonce length (expected size {})", .{algorithm.nonce_length}); 68 | 69 | const ad = try L.Zcheckvalue(?[]const u8, 5, null) orelse ""; 70 | 71 | const msg = try allocator.alloc(u8, cipher.len); 72 | defer allocator.free(msg); 73 | 74 | var stag: [algorithm.tag_length]u8 = undefined; 75 | @memcpy(stag[0..], tag[0..algorithm.tag_length]); 76 | var snonce: [algorithm.nonce_length]u8 = undefined; 77 | @memcpy(snonce[0..], nonce[0..algorithm.nonce_length]); 78 | var skey: [algorithm.key_length]u8 = undefined; 79 | @memcpy(skey[0..], key[0..algorithm.key_length]); 80 | 81 | try algorithm.decrypt(msg, cipher, stag, ad, snonce, skey); 82 | 83 | try L.pushlstring(msg); 84 | 85 | return 1; 86 | } 87 | }.decrypt; 88 | } 89 | -------------------------------------------------------------------------------- /src/core/standard/require.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const json = @import("json"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const LuaHelper = Zune.Utils.LuaHelper; 8 | 9 | const VM = luau.VM; 10 | 11 | pub const LIB_NAME = "require"; 12 | 13 | const LuaContext = struct { 14 | allocator: std.mem.Allocator, 15 | contents: ?[]const u8, 16 | accessed: bool = false, 17 | 18 | pub fn getConfig(self: *LuaContext, _: []const u8, err: ?*?[]const u8) !Zune.Resolvers.Config { 19 | if (self.accessed) 20 | return error.NotPresent; 21 | self.accessed = true; 22 | return Zune.Resolvers.Config.parse(self.allocator, self.contents orelse return error.NotPresent, err); 23 | } 24 | pub fn freeConfig(self: *LuaContext, config: *Zune.Resolvers.Config) void { 25 | config.deinit(self.allocator); 26 | } 27 | pub fn resolvePathAlloc(_: *LuaContext, a: std.mem.Allocator, from: []const u8, to: []const u8) ![]u8 { 28 | return std.fs.path.resolve(a, &.{ from, to }); 29 | } 30 | }; 31 | 32 | fn lua_navigate(L: *VM.lua.State) !i32 { 33 | const allocator = luau.getallocator(L); 34 | 35 | const path = try L.Zcheckvalue([]const u8, 1, null); 36 | const from = try L.Zcheckvalue(?[]const u8, 2, null); 37 | const config = try L.Zcheckvalue(?[]const u8, 3, null); 38 | 39 | var context: LuaContext = .{ 40 | .contents = config, 41 | .allocator = allocator, 42 | }; 43 | 44 | var ar: VM.lua.Debug = .{ .ssbuf = undefined }; 45 | { 46 | var level: i32 = 1; 47 | while (true) : (level += 1) { 48 | if (!L.getinfo(level, "s", &ar)) 49 | return L.Zerror("could not get source"); 50 | if (ar.what == .lua) 51 | break; 52 | } 53 | } 54 | 55 | const src = from orelse blk: { 56 | const ctx = ar.source orelse return error.BadContext; 57 | if (std.mem.startsWith(u8, ctx, "@")) { 58 | break :blk ctx[1..]; 59 | } else break :blk ctx; 60 | }; 61 | 62 | var err_msg: ?[]const u8 = null; 63 | defer if (err_msg) |msg| allocator.free(msg); 64 | const script_path = Zune.Resolvers.Navigator.navigate(allocator, &context, src, path, &err_msg) catch |err| switch (err) { 65 | error.SyntaxError, error.AliasNotFound, error.AliasPathNotSupported, error.AliasJumpFail => return L.Zerrorf("{s}", .{err_msg.?}), 66 | error.PathUnsupported => return L.Zerror("must have either \"@\", \"./\", or \"../\" prefix"), 67 | else => return err, 68 | }; 69 | defer allocator.free(script_path); 70 | 71 | try L.pushlstring(script_path); 72 | 73 | return 1; 74 | } 75 | 76 | fn lua_getCached(L: *VM.lua.State) !i32 { 77 | const resolved_path = try L.Zcheckvalue([:0]const u8, 1, null); 78 | _ = try L.Lfindtable(VM.lua.REGISTRYINDEX, "_MODULES", 1); 79 | 80 | _ = L.rawgetfield(-1, resolved_path); 81 | return 1; 82 | } 83 | 84 | pub fn loadLib(L: *VM.lua.State) !void { 85 | try L.Zpushvalue(.{ 86 | .navigate = lua_navigate, 87 | .getCached = lua_getCached, 88 | }); 89 | L.setreadonly(-1, true); 90 | try LuaHelper.registerModule(L, LIB_NAME); 91 | } 92 | 93 | test "require" { 94 | const TestRunner = @import("../utils/testrunner.zig"); 95 | 96 | const testResult = try TestRunner.runTest( 97 | TestRunner.newTestFile("standard/require/init.luau"), 98 | &.{}, 99 | .{}, 100 | ); 101 | 102 | try std.testing.expect(testResult.failed == 0); 103 | try std.testing.expect(testResult.total > 0); 104 | } 105 | -------------------------------------------------------------------------------- /test/standard/net/http/request.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local net = zune.net; 3 | local task = zune.task; 4 | local testing = zune.testing; 5 | 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | local _, version, _ = string.match(_VERSION, "(zune) (%d+%.%d+%.%d+.*)+(%d+%.%d+)"); 10 | assert(version and #version > 0, "No version"); 11 | 12 | test("Basic", function() 13 | local server = net.http.serve({ 14 | port = 8080, 15 | reuse_address = true, 16 | request = function(req) 17 | return { 18 | status_code = 200, 19 | body = "Hello", 20 | headers = { 21 | ["Content-Type"] = "text/plain", 22 | }, 23 | } 24 | end, 25 | }); 26 | testing.defer(function() 27 | server:stop(); 28 | end) 29 | local spawned = 10; 30 | for i = 1, spawned do 31 | local use_buffer = i > 5; 32 | task.spawn(function() 33 | local res = net.http.request("http://localhost:8080", { 34 | method = "GET", 35 | headers = { 36 | ["Content-Type"] = "text/plain", 37 | }, 38 | }, use_buffer); 39 | expect(res).toBe(expect.type("table")); 40 | expect(res.ok).toBe(true); 41 | expect(res.status_code).toBe(200); 42 | if (use_buffer) then 43 | expect(res.body).toBe(expect.type("buffer")); 44 | expect(buffer.tostring(res.body :: buffer)).toBe("Hello"); 45 | else 46 | expect(res.body).toBe("Hello"); 47 | end 48 | expect(res.headers).toBe(expect.type("table")); 49 | expect(res.headers["content-length"]).toBe("5"); 50 | expect(res.headers["content-type"]).toBe("text/plain"); 51 | expect(res.status_reason).toBe("OK"); 52 | spawned -= 1; 53 | end) 54 | end 55 | repeat 56 | task.wait(); 57 | until (spawned == 0); 58 | end) 59 | 60 | test("TLS", function() 61 | if (not table.find(zune.process.args, "allow")) then 62 | print(`Skip TLS test`); 63 | return; 64 | end 65 | do 66 | local res = net.http.request("https://github.com", { 67 | method = "GET", 68 | headers = { 69 | ["Content-Type"] = "text/plain", 70 | }, 71 | }, false); 72 | 73 | expect(res).toBe(expect.type("table")); 74 | expect(res.ok).toBe(true); 75 | expect(res.status_code).toBe(200); 76 | expect(res.body).toBe(expect.type("string")); 77 | expect(res.headers).toBe(expect.type("table")); 78 | expect(res.headers["content-type"]).toBe(expect.type("string")); 79 | end 80 | do 81 | -- should be able to use http protocol and tls port 82 | -- the tls config should force tls usage 83 | local res = net.http.request("http://github.com:443", { 84 | method = "GET", 85 | headers = { 86 | ["Content-Type"] = "text/plain", 87 | }, 88 | tls = { 89 | host = "github.com", 90 | ca = zune.crypto.tls.bundleFromSystem(), 91 | }, 92 | }, false); 93 | 94 | expect(res).toBe(expect.type("table")); 95 | expect(res.ok).toBe(true); 96 | expect(res.status_code).toBe(200); 97 | expect(res.body).toBe(expect.type("string")); 98 | expect(res.headers).toBe(expect.type("table")); 99 | expect(res.headers["content-type"]).toBe(expect.type("string")); 100 | end 101 | end) 102 | 103 | return nil; 104 | -------------------------------------------------------------------------------- /test/standard/serde/yaml.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Yaml", function() 10 | describe("Encoding", function() 11 | test("Boolean", function() 12 | expect(serde.yaml.encode(true)).toBe("true"); 13 | expect(serde.yaml.encode(false)).toBe("false"); 14 | end) 15 | test("Number", function() 16 | expect(serde.yaml.encode(123)).toBe("123"); 17 | expect(serde.yaml.encode(123.123)).toBe("123.123"); 18 | end) 19 | test("String", function() 20 | expect(serde.yaml.encode("foo bar")).toBe(`"foo bar"`); 21 | expect(serde.yaml.encode("foobar")).toBe(`"foobar"`); 22 | end) 23 | test("Map", function() 24 | expect(serde.yaml.encode({a = 1, b = 2, c = 3})) 25 | .toBe("a: 1\nc: 3\nb: 2"); 26 | expect(serde.yaml.encode({a = {b = {c = 3}}})) 27 | .toBe("a: \n b: \n c: 3"); 28 | expect(serde.yaml.encode({a = {b = {}}})) 29 | .toBe("a: \n b: \n"); 30 | end) 31 | end) 32 | 33 | describe("Decoding", function() 34 | test("Number", function() 35 | expect(serde.yaml.decode("123")).toBe("123"); 36 | expect(serde.yaml.decode("123.123")).toBe("123.123"); 37 | end) 38 | test("String", function() 39 | expect(serde.yaml.decode("foo bar")).toBe("foo bar"); 40 | end) 41 | test("Map", function() 42 | expect(serde.yaml.decode("a: 1\nb: 2\nc: 3")).toBe(expect.similar({ 43 | a = "1", 44 | b = "2", 45 | c = "3" 46 | })); 47 | expect(serde.yaml.decode("a:\n b:\n c: 3")).toBe(expect.similar({ 48 | a = { 49 | b = { 50 | c = "3" 51 | } 52 | }, 53 | })); 54 | expect(serde.yaml.decode("a:\n b: []")).toBe(expect.similar({ 55 | a = { 56 | b = {} 57 | }, 58 | })); 59 | end) 60 | test("List", function() 61 | expect(serde.yaml.decode("a: [1,2,3]")).toBe(expect.similar({ 62 | a = {"1","2","3"} 63 | })); 64 | expect(serde.yaml.decode("a: [\"foo\"]")).toBe(expect.similar({ 65 | a = { "foo" }, 66 | })); 67 | expect(serde.yaml.decode("a:\n b: []")).toBe(expect.similar({ 68 | a = { 69 | b = {} 70 | }, 71 | })); 72 | end) 73 | end) 74 | 75 | describe("Full", function() 76 | local full_table = {}; 77 | full_table["a"] = "a"; 78 | full_table["b"] = "100.20"; 79 | full_table["e"] = {"1", "2", "3"}; 80 | full_table["f"] = {a = "a", b = "100.20"}; 81 | -- TODO: support encoded strings 82 | -- full_table["some_encoded_name"] = "someencoded\0value\1\n\n"; 83 | full_table["g"] = table.clone(full_table); 84 | full_table["g"]["__sub"] = table.clone(full_table); 85 | full_table["h"] = table.clone(full_table); 86 | 87 | -- fixes 88 | full_table["g"]["__sub"]["g"] = nil :: any; -- circular reference 89 | full_table["h"]["__sub"] = nil :: any; -- circular reference 90 | full_table["h"]["g"] = nil :: any; -- circular reference 91 | 92 | test("Encode/Decode", function() 93 | expect(serde.yaml.decode(serde.yaml.encode(full_table :: any))).toBe(expect.similar(full_table)); 94 | end) 95 | end) 96 | end) 97 | 98 | return nil; 99 | -------------------------------------------------------------------------------- /src/core/standard/crypto/password.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const VM = luau.VM; 5 | 6 | const argon2 = std.crypto.pwhash.argon2; 7 | const bcrypt = std.crypto.pwhash.bcrypt; 8 | 9 | const AlgorithmUnion = union(enum) { 10 | argon2: argon2.Mode, 11 | bcrypt: void, 12 | }; 13 | 14 | const DEFAULT_ALGO: AlgorithmUnion = .{ .argon2 = .argon2id }; 15 | const AlgorithmMap = std.StaticStringMap(AlgorithmUnion).initComptime(.{ 16 | .{ "argon2d", AlgorithmUnion{ .argon2 = .argon2d } }, 17 | .{ "argon2i", AlgorithmUnion{ .argon2 = .argon2i } }, 18 | .{ "argon2id", DEFAULT_ALGO }, 19 | .{ "bcrypt", .bcrypt }, 20 | }); 21 | 22 | pub fn lua_hash(L: *VM.lua.State) !i32 { 23 | const allocator = luau.getallocator(L); 24 | const password = try L.Zcheckvalue([]const u8, 1, null); 25 | 26 | var algorithm = DEFAULT_ALGO; 27 | var cost: u32 = 65536; 28 | var cost2: u32 = 2; 29 | var threads: u24 = 1; 30 | 31 | switch (L.typeOf(2)) { 32 | .Table => { 33 | if (try L.Zcheckfield(?[:0]const u8, 2, "algorithm")) |option| 34 | algorithm = AlgorithmMap.get(option) orelse return L.Zerror("invalid algorithm kind"); 35 | L.pop(1); 36 | switch (algorithm) { 37 | .argon2 => { 38 | cost = try L.Zcheckfield(?u32, 2, "memory_cost") orelse cost; 39 | cost2 = try L.Zcheckfield(?u32, 2, "time_cost") orelse cost2; 40 | threads = try L.Zcheckfield(?u24, 2, "threads") orelse threads; 41 | L.pop(3); 42 | }, 43 | .bcrypt => { 44 | cost = 4; 45 | cost = try L.Zcheckfield(?u32, 2, "cost") orelse cost; 46 | if (cost < 4 or cost > 31) 47 | return L.Zerror("invalid cost (Must be between 4 to 31)"); 48 | L.pop(1); 49 | }, 50 | } 51 | }, 52 | .None, .Nil => {}, 53 | else => try L.Zchecktype(2, .Table), 54 | } 55 | 56 | var buf: [128]u8 = undefined; 57 | switch (algorithm) { 58 | .argon2 => |mode| { 59 | const hash = try argon2.strHash(password, .{ 60 | .allocator = allocator, 61 | .params = .{ .m = cost, .t = cost2, .p = threads }, 62 | .mode = mode, 63 | }, &buf); 64 | try L.pushlstring(hash); 65 | }, 66 | .bcrypt => { 67 | const hash = try bcrypt.strHash(password, .{ 68 | .allocator = allocator, 69 | .params = .{ .rounds_log = @intCast(cost), .silently_truncate_password = false }, 70 | .encoding = .phc, 71 | }, &buf); 72 | try L.pushlstring(hash); 73 | }, 74 | } 75 | return 1; 76 | } 77 | 78 | const TAG_BCRYPT: u32 = @bitCast([4]u8{ '$', 'b', 'c', 'r' }); 79 | 80 | pub fn lua_verify(L: *VM.lua.State) !i32 { 81 | const allocator = luau.getallocator(L); 82 | const password = try L.Zcheckvalue([]const u8, 1, null); 83 | const hash = try L.Zcheckvalue([]const u8, 2, null); 84 | 85 | if (hash.len < 8) 86 | return L.Zerror("invalid hash (must be PHC encoded)"); 87 | 88 | if (@as(u32, @bitCast(hash[0..4].*)) == TAG_BCRYPT) 89 | L.pushboolean(if (bcrypt.strVerify( 90 | hash, 91 | password, 92 | .{ 93 | .allocator = allocator, 94 | .silently_truncate_password = false, 95 | }, 96 | )) true else |_| false) 97 | else 98 | L.pushboolean(if (argon2.strVerify( 99 | hash, 100 | password, 101 | .{ .allocator = allocator }, 102 | )) true else |_| false); 103 | 104 | return 1; 105 | } 106 | -------------------------------------------------------------------------------- /legacy/compress/flate/bit_writer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | /// Bit writer for use in deflate (compression). 5 | /// 6 | /// Has internal bits buffer of 64 bits and internal bytes buffer of 248 bytes. 7 | /// When we accumulate 48 bits 6 bytes are moved to the bytes buffer. When we 8 | /// accumulate 240 bytes they are flushed to the underlying inner_writer. 9 | /// 10 | pub fn BitWriter(comptime WriterType: type) type { 11 | // buffer_flush_size indicates the buffer size 12 | // after which bytes are flushed to the writer. 13 | // Should preferably be a multiple of 6, since 14 | // we accumulate 6 bytes between writes to the buffer. 15 | const buffer_flush_size = 240; 16 | 17 | // buffer_size is the actual output byte buffer size. 18 | // It must have additional headroom for a flush 19 | // which can contain up to 8 bytes. 20 | const buffer_size = buffer_flush_size + 8; 21 | 22 | return struct { 23 | inner_writer: WriterType, 24 | 25 | // Data waiting to be written is bytes[0 .. nbytes] 26 | // and then the low nbits of bits. Data is always written 27 | // sequentially into the bytes array. 28 | bits: u64 = 0, 29 | nbits: u32 = 0, // number of bits 30 | bytes: [buffer_size]u8 = undefined, 31 | nbytes: u32 = 0, // number of bytes 32 | 33 | const Self = @This(); 34 | 35 | pub const Error = WriterType.Error || error{UnfinishedBits}; 36 | 37 | pub fn init(writer: WriterType) Self { 38 | return .{ .inner_writer = writer }; 39 | } 40 | 41 | pub fn setWriter(self: *Self, new_writer: WriterType) void { 42 | //assert(self.bits == 0 and self.nbits == 0 and self.nbytes == 0); 43 | self.inner_writer = new_writer; 44 | } 45 | 46 | pub fn flush(self: *Self) Error!void { 47 | var n = self.nbytes; 48 | while (self.nbits != 0) { 49 | self.bytes[n] = @as(u8, @truncate(self.bits)); 50 | self.bits >>= 8; 51 | if (self.nbits > 8) { // Avoid underflow 52 | self.nbits -= 8; 53 | } else { 54 | self.nbits = 0; 55 | } 56 | n += 1; 57 | } 58 | self.bits = 0; 59 | _ = try self.inner_writer.write(self.bytes[0..n]); 60 | self.nbytes = 0; 61 | } 62 | 63 | pub fn writeBits(self: *Self, b: u32, nb: u32) Error!void { 64 | self.bits |= @as(u64, @intCast(b)) << @as(u6, @intCast(self.nbits)); 65 | self.nbits += nb; 66 | if (self.nbits < 48) 67 | return; 68 | 69 | var n = self.nbytes; 70 | std.mem.writeInt(u64, self.bytes[n..][0..8], self.bits, .little); 71 | n += 6; 72 | if (n >= buffer_flush_size) { 73 | _ = try self.inner_writer.write(self.bytes[0..n]); 74 | n = 0; 75 | } 76 | self.nbytes = n; 77 | self.bits >>= 48; 78 | self.nbits -= 48; 79 | } 80 | 81 | pub fn writeBytes(self: *Self, bytes: []const u8) Error!void { 82 | var n = self.nbytes; 83 | if (self.nbits & 7 != 0) { 84 | return error.UnfinishedBits; 85 | } 86 | while (self.nbits != 0) { 87 | self.bytes[n] = @as(u8, @truncate(self.bits)); 88 | self.bits >>= 8; 89 | self.nbits -= 8; 90 | n += 1; 91 | } 92 | if (n != 0) { 93 | _ = try self.inner_writer.write(self.bytes[0..n]); 94 | } 95 | self.nbytes = 0; 96 | _ = try self.inner_writer.write(bytes); 97 | } 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /test/standard/serde/zstd.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Zlib", function() 10 | local sample = string.rep([[ 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 12 | ]], 20); 13 | local compressed_sample_default = "(\xB5/\xFD`d\x04M\x02\x00\xC4\x03 Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n \x03\x00\x14!\xE1\x03\x05\x11b\x00\v"; 14 | local compressed_sample_1 = "(\xB5/\xFD`d\x04M\x02\x00\xC4\x03 Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n \x03\x00\x14!\xE1\x03\x05\x11b\x00\v"; 15 | local compressed_sample_6 = "(\xB5/\xFD`d\x04E\x02\x00\xB4\x03 Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n \x03\x00\x14!\xE1\x03\x05\x11p\x1D\x1B"; 16 | local compressed_sample_10 = "(\xB5/\xFD`d\x04M\x02\x00\xC4\x03 Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n \x03\x00\x14!\xE1\x03\x05\x11b\x00\v"; 17 | local compressed_sample_22 = "(\xB5/\xFD`d\x04\xED\x01\x00\xE2C\f\x10\xB0k\f\xB2\xB7L\x90\x8D\xD8\x83\x8Es\xD1\xBCE\f\xFF@,\xA2\xFB(,\x8A\v\r\x99\xB5\xBD-\xA7*\xCBa/\x91\x17u\x96(\xCD\xA9\xE9\xC2\xD3\xB1\x1C\a\x02\x00\xCD\x11\x12>\xB8\x80\r"; 18 | 19 | describe("Compression", function() 20 | test("Default", function() 21 | expect(serde.zstd.compress(sample)).toBe(compressed_sample_default); 22 | end) 23 | test("Buffer (Default)", function() 24 | local buf = serde.zstd.compress(buffer.fromstring(sample)); 25 | expect(buf).toBe(expect.type("buffer")); 26 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 27 | end) 28 | test("Level 1", function() 29 | expect(serde.zstd.compress(sample, {level = 1})).toBe(compressed_sample_1); 30 | end) 31 | test("Level 6", function() 32 | expect(serde.zstd.compress(sample, {level = 6})).toBe(compressed_sample_6); 33 | end) 34 | test("Level 10", function() 35 | expect(serde.zstd.compress(sample, {level = 10})).toBe(compressed_sample_10); 36 | end) 37 | test("Level 22", function() 38 | expect(serde.zstd.compress(sample, {level = 22})).toBe(compressed_sample_22); 39 | end) 40 | test("Fail", function() 41 | expect(function() serde.zstd.compress(sample, {level = 23}) end).toThrow("options 'level' must not be over 22 or less than -131072"); 42 | expect(function() serde.zstd.compress(sample, {level = -131073}) end).toThrow("options 'level' must not be over 22 or less than -131072"); 43 | expect(function() serde.zstd.compress(true) end).toThrow("invalid argument #1 to 'compress' (string expected, got boolean)"); 44 | end) 45 | end) 46 | 47 | describe("Decompression", function() 48 | test("Default", function() 49 | expect(serde.zstd.decompress(compressed_sample_default)).toBe(sample); 50 | end) 51 | test("Buffer (Default)", function() 52 | local buf = serde.zstd.decompress(buffer.fromstring(compressed_sample_default)); 53 | expect(buf).toBe(expect.type("buffer")); 54 | expect(buffer.tostring(buf)).toBe(sample); 55 | end) 56 | test("Level 1", function() 57 | expect(serde.zstd.decompress(compressed_sample_1)).toBe(sample); 58 | end) 59 | test("Level 6", function() 60 | expect(serde.zstd.decompress(compressed_sample_6)).toBe(sample); 61 | end) 62 | test("Level 10", function() 63 | expect(serde.zstd.decompress(compressed_sample_10)).toBe(sample); 64 | end) 65 | test("Level 22", function() 66 | expect(serde.zstd.decompress(compressed_sample_22)).toBe(sample); 67 | end) 68 | test("Fail", function() 69 | expect(function() serde.zstd.decompress(buffer.create(20)) end).toThrow("UnknownPrefix"); 70 | end) 71 | end) 72 | end) 73 | 74 | return nil; 75 | -------------------------------------------------------------------------------- /src/core/standard/net/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const builtin = @import("builtin"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const Scheduler = Zune.Runtime.Scheduler; 8 | 9 | const LuaHelper = Zune.Utils.LuaHelper; 10 | 11 | const VM = luau.VM; 12 | 13 | const Socket = @import("../../objects/network/Socket.zig"); 14 | 15 | pub const LIB_NAME = "net"; 16 | pub fn PlatformSupported() bool { 17 | return switch (comptime builtin.os.tag) { 18 | .linux, .macos, .windows => true, 19 | .freebsd => true, 20 | else => false, 21 | }; 22 | } 23 | 24 | pub fn createSocket(domain: u32, flags: u32, protocol: u32) !std.posix.socket_t { 25 | return switch (comptime builtin.os.tag) { 26 | .windows => try @import("../../utils/os/windows.zig").socket(domain, flags, protocol), 27 | else => try std.posix.socket(domain, flags, protocol), 28 | }; 29 | } 30 | 31 | fn lua_createSocket(L: *VM.lua.State) !i32 { 32 | if (!L.isyieldable()) 33 | return L.Zyielderror(); 34 | const domain = L.Lcheckunsigned(1); 35 | const flags = L.Lcheckunsigned(2); 36 | const protocol = L.Lcheckunsigned(3); 37 | 38 | const socket = try createSocket(domain, flags, protocol); 39 | 40 | _ = try Socket.push(L, socket, .created); 41 | 42 | return 1; 43 | } 44 | 45 | fn lua_getAddressList(L: *VM.lua.State) !i32 { 46 | const allocator = luau.getallocator(L); 47 | const name = L.Lcheckstring(1); 48 | const port = L.Lcheckunsigned(2); 49 | if (port > std.math.maxInt(u16)) 50 | return L.Zerror("port out of range"); 51 | const list = try std.net.getAddressList(allocator, name, @intCast(port)); 52 | defer list.deinit(); 53 | if (list.addrs.len > std.math.maxInt(i32)) 54 | return L.Zerror("address list too large"); 55 | try L.createtable(@intCast(list.addrs.len), 0); 56 | for (list.addrs, 1..) |address, i| { 57 | var buf: [Socket.LONGEST_ADDRESS]u8 = undefined; 58 | try L.Zpushvalue(.{ 59 | .family = address.any.family, 60 | .port = address.getPort(), 61 | .address = Socket.AddressToString(&buf, address), 62 | }); 63 | try L.rawseti(-2, @intCast(i)); 64 | } 65 | return 1; 66 | } 67 | 68 | fn ImportConstants(L: *VM.lua.State, namespace: anytype, comptime name: [:0]const u8) !void { 69 | try L.createtable(0, @typeInfo(namespace).@"struct".decls.len); 70 | 71 | inline for (@typeInfo(namespace).@"struct".decls) |field| 72 | try L.Zsetfield(-1, field.name, @as(i32, @field(namespace, field.name))); 73 | 74 | L.setreadonly(-1, true); 75 | try L.rawsetfield(-2, name); 76 | } 77 | 78 | pub fn loadLib(L: *VM.lua.State) !void { 79 | try L.createtable(0, 8); 80 | 81 | try L.Zsetfieldfn(-1, "createSocket", lua_createSocket); 82 | try L.Zsetfieldfn(-1, "getAddressList", lua_getAddressList); 83 | 84 | try ImportConstants(L, std.posix.AF, "ADDRF"); 85 | try ImportConstants(L, std.posix.SOCK, "SOCKF"); 86 | try ImportConstants(L, std.posix.IPPROTO, "IPPROTO"); 87 | try ImportConstants(L, std.posix.SO, "SOCKOPT"); 88 | try ImportConstants(L, std.posix.SOL, "SOCKOPTLV"); 89 | 90 | { 91 | try @import("http/lib.zig").load(L); 92 | try L.rawsetfield(-2, "http"); 93 | } 94 | 95 | L.setreadonly(-1, true); 96 | 97 | try LuaHelper.registerModule(L, LIB_NAME); 98 | } 99 | 100 | test { 101 | _ = @import("http/request.zig"); 102 | } 103 | test { 104 | _ = @import("http/response.zig"); 105 | } 106 | test { 107 | _ = @import("http/websocket.zig"); 108 | } 109 | 110 | test "net" { 111 | const TestRunner = @import("../../utils/testrunner.zig"); 112 | 113 | const testResult = try TestRunner.runTest( 114 | TestRunner.newTestFile("standard/net/init.luau"), 115 | &.{}, 116 | .{}, 117 | ); 118 | 119 | try std.testing.expect(testResult.failed == 0); 120 | try std.testing.expect(testResult.total > 0); 121 | } 122 | -------------------------------------------------------------------------------- /test/runner.zig: -------------------------------------------------------------------------------- 1 | // This code is based on https://gist.github.com/nurpax/4afcb6e4ef3f03f0d282f7c462005f12 2 | const std = @import("std"); 3 | const builtin = @import("builtin"); 4 | 5 | const Status = enum { 6 | Passed, 7 | Failed, 8 | Skipped, 9 | }; 10 | 11 | fn getenvOwned(alloc: std.mem.Allocator, key: []const u8) ?[]u8 { 12 | const v = std.process.getEnvVarOwned(alloc, key) catch |err| { 13 | if (err == error.EnvironmentVariableNotFound) 14 | return null; 15 | std.log.warn("Failed to get env var {s} due to err {}", .{ key, err }); 16 | return null; 17 | }; 18 | return v; 19 | } 20 | 21 | const Printer = struct { 22 | out: *std.Io.Writer, 23 | 24 | inline fn fmt(self: *Printer, comptime format: []const u8, args: anytype) void { 25 | self.out.print(format, args) catch @panic("OOM"); 26 | } 27 | 28 | fn status(self: *Printer, s: Status, comptime format: []const u8, args: anytype) void { 29 | self.out.writeAll(switch (s) { 30 | .Failed => "\x1b[31m", 31 | .Passed => "\x1b[32m", 32 | .Skipped => "\x1b[33m", 33 | }) catch @panic("OOM"); 34 | self.fmt(format, args); 35 | self.fmt("\x1b[0m", .{}); 36 | self.out.flush() catch @panic("OOM"); 37 | } 38 | }; 39 | 40 | pub fn main() !void { 41 | var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){}; 42 | const alloc = gpa.allocator(); 43 | const fail_first = blk: { 44 | if (getenvOwned(alloc, "TEST_FAIL_FIRST")) |e| { 45 | defer alloc.free(e); 46 | break :blk std.mem.eql(u8, e, "true"); 47 | } 48 | break :blk false; 49 | }; 50 | 51 | const stderr = std.fs.File.stderr(); 52 | var buffer: [1024]u8 = undefined; 53 | var err_writer = stderr.writer(&buffer); 54 | const writer = &err_writer.interface; 55 | var printer: Printer = .{ 56 | .out = writer, 57 | }; 58 | 59 | var passed: usize = 0; 60 | var failed: usize = 0; 61 | var skipped: usize = 0; 62 | var leaked: usize = 0; 63 | 64 | for (builtin.test_functions) |t| { 65 | std.testing.allocator_instance = .{}; 66 | var status = Status.Passed; 67 | 68 | std.debug.print("{s}...\n", .{t.name}); 69 | 70 | const result = t.func(); 71 | 72 | if (std.testing.allocator_instance.deinit() == .leak) { 73 | leaked += 1; 74 | printer.status(.Failed, "[Error] \"{s}\" (Memory Leak)\n", .{t.name}); 75 | } 76 | 77 | if (result) |_| 78 | passed += 1 79 | else |err| { 80 | switch (err) { 81 | error.SkipZigTest => { 82 | skipped += 1; 83 | status = .Skipped; 84 | }, 85 | else => { 86 | status = .Failed; 87 | failed += 1; 88 | printer.status(.Failed, "[Error] \"{s}\": {s}\n", .{ t.name, @errorName(err) }); 89 | if (@errorReturnTrace()) |trace| 90 | std.debug.dumpStackTrace(trace.*); 91 | if (fail_first) 92 | break; 93 | }, 94 | } 95 | } 96 | 97 | printer.fmt("{s} ", .{t.name}); 98 | printer.status(status, "[{s}]\n", .{@tagName(status)}); 99 | } 100 | 101 | const total_tests = passed + failed; 102 | const status: Status = if (failed == 0) 103 | .Passed 104 | else 105 | .Failed; 106 | printer.status(status, "\n{d} of {d} test{s} passed\n", .{ passed, total_tests, if (total_tests != 1) "s" else "" }); 107 | if (skipped > 0) 108 | printer.status(.Skipped, "{d} test{s} skipped\n", .{ skipped, if (skipped != 1) "s" else "" }); 109 | if (leaked > 0) 110 | printer.status(.Failed, "{d} test{s} leaked\n", .{ leaked, if (leaked != 1) "s" else "" }); 111 | std.process.exit(if (failed == 0 and leaked == 0) 0 else 1); 112 | } 113 | -------------------------------------------------------------------------------- /test/standard/net/socket.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local net = zune.net; 3 | local task = zune.task; 4 | local testing = zune.testing; 5 | 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | test("Basic TCP", function() 10 | local server_socket = net.createSocket( 11 | net.ADDRF.INET, 12 | bit32.bor(net.SOCKF.STREAM, net.SOCKF.CLOEXEC, net.SOCKF.NONBLOCK), 13 | net.IPPROTO.TCP 14 | ); 15 | 16 | server_socket:setOption(net.SOCKOPTLV.SOCKET, net.SOCKOPT.REUSEADDR, true); 17 | if (net.SOCKOPT.REUSEPORT) then 18 | server_socket:setOption(net.SOCKOPTLV.SOCKET, net.SOCKOPT.REUSEPORT, true); 19 | end 20 | 21 | server_socket:bindIp("127.0.0.1", 0); 22 | server_socket:listen(2); 23 | local server_sockname = server_socket:getName(); 24 | 25 | local client_socket = net.createSocket( 26 | server_sockname.family, 27 | bit32.bor( 28 | net.SOCKF.STREAM, 29 | if (zune.platform.os == "windows") then 0 else net.SOCKF.CLOEXEC 30 | ), 31 | net.IPPROTO.TCP 32 | ); 33 | 34 | task.spawn(function() 35 | local connection_socket = server_socket:accept(); 36 | 37 | for i = 1, 3 do 38 | connection_socket:send(`Hello from server {i}`); 39 | local bytes = connection_socket:recv(19); 40 | expect(buffer.tostring(bytes)).toBe(`Hello from client {i}`); 41 | end 42 | 43 | connection_socket:close(); 44 | end) 45 | 46 | client_socket:connect("127.0.0.1", server_sockname.port); 47 | 48 | for i = 1, 3 do 49 | local bytes = client_socket:recv(19); 50 | expect(buffer.tostring(bytes)).toBe(`Hello from server {i}`); 51 | client_socket:send(`Hello from client {i}`); 52 | end 53 | 54 | client_socket:close(); 55 | 56 | task.wait(0.5); 57 | end) 58 | 59 | test("Basic TCP TLS", function() 60 | if (zune.process.args[1] ~= "allow") then 61 | print("Skipping TLS test"); 62 | return; 63 | end 64 | 65 | local server_socket = net.createSocket( 66 | net.ADDRF.INET, 67 | bit32.bor(net.SOCKF.STREAM, net.SOCKF.CLOEXEC, net.SOCKF.NONBLOCK), 68 | net.IPPROTO.TCP 69 | ); 70 | 71 | zune.crypto.tls.setupServer(server_socket, { 72 | auth = zune.crypto.tls.keyPairFromFile("test/standard/net/cert.pem", "test/standard/net/key.pem"), 73 | }); 74 | 75 | server_socket:setOption(net.SOCKOPTLV.SOCKET, net.SOCKOPT.REUSEADDR, true); 76 | if (net.SOCKOPT.REUSEPORT) then 77 | server_socket:setOption(net.SOCKOPTLV.SOCKET, net.SOCKOPT.REUSEPORT, true); 78 | end 79 | 80 | server_socket:bindIp("127.0.0.1", 0); 81 | server_socket:listen(2); 82 | local server_sockname = server_socket:getName(); 83 | 84 | local client_socket = net.createSocket( 85 | server_sockname.family, 86 | bit32.bor( 87 | net.SOCKF.STREAM, 88 | if (zune.platform.os == "windows") then 0 else net.SOCKF.CLOEXEC 89 | ), 90 | net.IPPROTO.TCP 91 | ); 92 | 93 | zune.crypto.tls.setupClient(client_socket, { 94 | host = "", 95 | ca = zune.crypto.tls.bundleFromFile("test/standard/net/cert.pem"), 96 | }); 97 | 98 | task.spawn(function() 99 | local connection_socket = server_socket:accept(); 100 | 101 | for i = 1, 3 do 102 | connection_socket:send(`Hello from server {i}`); 103 | local bytes = connection_socket:recv(19); 104 | expect(buffer.tostring(bytes)).toBe(`Hello from client {i}`); 105 | end 106 | connection_socket:close(); 107 | end) 108 | 109 | client_socket:connect("127.0.0.1", server_sockname.port); 110 | 111 | for i = 1, 3 do 112 | local bytes = client_socket:recv(19); 113 | expect(buffer.tostring(bytes)).toBe(`Hello from server {i}`); 114 | client_socket:send(`Hello from client {i}`); 115 | end 116 | 117 | client_socket:close(); 118 | 119 | task.wait(0.5); 120 | end) 121 | 122 | return nil; 123 | -------------------------------------------------------------------------------- /legacy/compress/flate/Lookup.zig: -------------------------------------------------------------------------------- 1 | //! Lookup of the previous locations for the same 4 byte data. Works on hash of 2 | //! 4 bytes data. Head contains position of the first match for each hash. Chain 3 | //! points to the previous position of the same hash given the current location. 4 | 5 | const std = @import("std"); 6 | const testing = std.testing; 7 | const expect = testing.expect; 8 | const consts = @import("consts.zig"); 9 | 10 | const Self = @This(); 11 | 12 | const prime4 = 0x9E3779B1; // 4 bytes prime number 2654435761 13 | const chain_len = 2 * consts.history.len; 14 | 15 | // Maps hash => first position 16 | head: [consts.lookup.len]u16 = [_]u16{0} ** consts.lookup.len, 17 | // Maps position => previous positions for the same hash value 18 | chain: [chain_len]u16 = [_]u16{0} ** (chain_len), 19 | 20 | // Calculates hash of the 4 bytes from data. 21 | // Inserts `pos` position of that hash in the lookup tables. 22 | // Returns previous location with the same hash value. 23 | pub fn add(self: *Self, data: []const u8, pos: u16) u16 { 24 | if (data.len < 4) return 0; 25 | const h = hash(data[0..4]); 26 | return self.set(h, pos); 27 | } 28 | 29 | // Returns previous location with the same hash value given the current 30 | // position. 31 | pub fn prev(self: *Self, pos: u16) u16 { 32 | return self.chain[pos]; 33 | } 34 | 35 | fn set(self: *Self, h: u32, pos: u16) u16 { 36 | const p = self.head[h]; 37 | self.head[h] = pos; 38 | self.chain[pos] = p; 39 | return p; 40 | } 41 | 42 | // Slide all positions in head and chain for `n` 43 | pub fn slide(self: *Self, n: u16) void { 44 | for (&self.head) |*v| { 45 | v.* -|= n; 46 | } 47 | var i: usize = 0; 48 | while (i < n) : (i += 1) { 49 | self.chain[i] = self.chain[i + n] -| n; 50 | } 51 | } 52 | 53 | // Add `len` 4 bytes hashes from `data` into lookup. 54 | // Position of the first byte is `pos`. 55 | pub fn bulkAdd(self: *Self, data: []const u8, len: u16, pos: u16) void { 56 | if (len == 0 or data.len < consts.match.min_length) { 57 | return; 58 | } 59 | var hb = 60 | @as(u32, data[3]) | 61 | @as(u32, data[2]) << 8 | 62 | @as(u32, data[1]) << 16 | 63 | @as(u32, data[0]) << 24; 64 | _ = self.set(hashu(hb), pos); 65 | 66 | var i = pos; 67 | for (4..@min(len + 3, data.len)) |j| { 68 | hb = (hb << 8) | @as(u32, data[j]); 69 | i += 1; 70 | _ = self.set(hashu(hb), i); 71 | } 72 | } 73 | 74 | // Calculates hash of the first 4 bytes of `b`. 75 | fn hash(b: *const [4]u8) u32 { 76 | return hashu(@as(u32, b[3]) | 77 | @as(u32, b[2]) << 8 | 78 | @as(u32, b[1]) << 16 | 79 | @as(u32, b[0]) << 24); 80 | } 81 | 82 | fn hashu(v: u32) u32 { 83 | return @intCast((v *% prime4) >> consts.lookup.shift); 84 | } 85 | 86 | test add { 87 | const data = [_]u8{ 88 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 89 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 90 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 91 | 0x01, 0x02, 0x03, 92 | }; 93 | 94 | var h: Self = .{}; 95 | for (data, 0..) |_, i| { 96 | const p = h.add(data[i..], @intCast(i)); 97 | if (i >= 8 and i < 24) { 98 | try expect(p == i - 8); 99 | } else { 100 | try expect(p == 0); 101 | } 102 | } 103 | 104 | const v = Self.hash(data[2 .. 2 + 4]); 105 | try expect(h.head[v] == 2 + 16); 106 | try expect(h.chain[2 + 16] == 2 + 8); 107 | try expect(h.chain[2 + 8] == 2); 108 | } 109 | 110 | test bulkAdd { 111 | const data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; 112 | 113 | // one by one 114 | var h: Self = .{}; 115 | for (data, 0..) |_, i| { 116 | _ = h.add(data[i..], @intCast(i)); 117 | } 118 | 119 | // in bulk 120 | var bh: Self = .{}; 121 | bh.bulkAdd(data, data.len, 0); 122 | 123 | try testing.expectEqualSlices(u16, &h.head, &bh.head); 124 | try testing.expectEqualSlices(u16, &h.chain, &bh.chain); 125 | } 126 | -------------------------------------------------------------------------------- /legacy/compress/zlib.zig: -------------------------------------------------------------------------------- 1 | const deflate = @import("flate/deflate.zig"); 2 | const inflate = @import("flate/inflate.zig"); 3 | 4 | /// Decompress compressed data from reader and write plain data to the writer. 5 | pub fn decompress(reader: anytype, writer: anytype) !void { 6 | try inflate.decompress(.zlib, reader, writer); 7 | } 8 | 9 | /// Decompressor type 10 | pub fn Decompressor(comptime ReaderType: type) type { 11 | return inflate.Decompressor(.zlib, ReaderType); 12 | } 13 | 14 | /// Create Decompressor which will read compressed data from reader. 15 | pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) { 16 | return inflate.decompressor(.zlib, reader); 17 | } 18 | 19 | /// Compression level, trades between speed and compression size. 20 | pub const Options = deflate.Options; 21 | 22 | /// Compress plain data from reader and write compressed data to the writer. 23 | pub fn compress(reader: anytype, writer: anytype, options: Options) !void { 24 | try deflate.compress(.zlib, reader, writer, options); 25 | } 26 | 27 | /// Compressor type 28 | pub fn Compressor(comptime WriterType: type) type { 29 | return deflate.Compressor(.zlib, WriterType); 30 | } 31 | 32 | /// Create Compressor which outputs compressed data to the writer. 33 | pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) { 34 | return try deflate.compressor(.zlib, writer, options); 35 | } 36 | 37 | /// Huffman only compression. Without Lempel-Ziv match searching. Faster 38 | /// compression, less memory requirements but bigger compressed sizes. 39 | pub const huffman = struct { 40 | pub fn compress(reader: anytype, writer: anytype) !void { 41 | try deflate.huffman.compress(.zlib, reader, writer); 42 | } 43 | 44 | pub fn Compressor(comptime WriterType: type) type { 45 | return deflate.huffman.Compressor(.zlib, WriterType); 46 | } 47 | 48 | pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) { 49 | return deflate.huffman.compressor(.zlib, writer); 50 | } 51 | }; 52 | 53 | // No compression store only. Compressed size is slightly bigger than plain. 54 | pub const store = struct { 55 | pub fn compress(reader: anytype, writer: anytype) !void { 56 | try deflate.store.compress(.zlib, reader, writer); 57 | } 58 | 59 | pub fn Compressor(comptime WriterType: type) type { 60 | return deflate.store.Compressor(.zlib, WriterType); 61 | } 62 | 63 | pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) { 64 | return deflate.store.compressor(.zlib, writer); 65 | } 66 | }; 67 | 68 | test "should not overshoot" { 69 | const std = @import("std"); 70 | 71 | // Compressed zlib data with extra 4 bytes at the end. 72 | const data = [_]u8{ 73 | 0x78, 0x9c, 0x73, 0xce, 0x2f, 0xa8, 0x2c, 0xca, 0x4c, 0xcf, 0x28, 0x51, 0x08, 0xcf, 0xcc, 0xc9, 74 | 0x49, 0xcd, 0x55, 0x28, 0x4b, 0xcc, 0x53, 0x08, 0x4e, 0xce, 0x48, 0xcc, 0xcc, 0xd6, 0x51, 0x08, 75 | 0xce, 0xcc, 0x4b, 0x4f, 0x2c, 0xc8, 0x2f, 0x4a, 0x55, 0x30, 0xb4, 0xb4, 0x34, 0xd5, 0xb5, 0x34, 76 | 0x03, 0x00, 0x8b, 0x61, 0x0f, 0xa4, 0x52, 0x5a, 0x94, 0x12, 77 | }; 78 | 79 | var stream = std.io.fixedBufferStream(data[0..]); 80 | const reader = stream.reader(); 81 | 82 | var dcp = decompressor(reader); 83 | var out: [128]u8 = undefined; 84 | 85 | // Decompress 86 | var n = try dcp.reader().readAll(out[0..]); 87 | 88 | // Expected decompressed data 89 | try std.testing.expectEqual(46, n); 90 | try std.testing.expectEqualStrings("Copyright Willem van Schaik, Singapore 1995-96", out[0..n]); 91 | 92 | // Decompressor don't overshoot underlying reader. 93 | // It is leaving it at the end of compressed data chunk. 94 | try std.testing.expectEqual(data.len - 4, stream.getPos()); 95 | try std.testing.expectEqual(0, dcp.unreadBytes()); 96 | 97 | // 4 bytes after compressed chunk are available in reader. 98 | n = try reader.readAll(out[0..]); 99 | try std.testing.expectEqual(n, 4); 100 | try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]); 101 | } 102 | -------------------------------------------------------------------------------- /src/core/standard/serde/lz4.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const lz4 = @import("lz4"); 4 | 5 | const Zune = @import("zune"); 6 | 7 | const VM = luau.VM; 8 | 9 | // Lune compatibility 10 | 11 | pub fn lua_frame_compress(L: *VM.lua.State) !i32 { 12 | const allocator = luau.getallocator(L); 13 | 14 | const is_buffer = L.typeOf(1) == .Buffer; 15 | 16 | const string = if (is_buffer) 17 | L.Lcheckbuffer(1) 18 | else 19 | L.Lcheckstring(1); 20 | 21 | var level: u32 = 4; 22 | 23 | if (!L.typeOf(2).isnoneornil()) { 24 | try L.Zchecktype(2, .Table); 25 | const levelType = L.rawgetfield(2, "level"); 26 | if (!levelType.isnoneornil()) { 27 | if (levelType != .Number) 28 | return L.Zerror("options 'level' field must be a number"); 29 | const num = L.tointeger(-1) orelse unreachable; 30 | if (num < 0) 31 | return L.Zerror("options 'level' must not be less than 0"); 32 | level = @intCast(num); 33 | } 34 | L.pop(1); 35 | } 36 | 37 | var encoder = try lz4.Encoder.init(allocator); 38 | _ = encoder.setLevel(level) 39 | .setContentChecksum(lz4.Frame.ContentChecksum.Enabled) 40 | .setBlockMode(lz4.Frame.BlockMode.Independent); 41 | defer encoder.deinit(); 42 | 43 | var allocating: std.Io.Writer.Allocating = .init(allocator); 44 | defer allocating.deinit(); 45 | 46 | try encoder.compressStream(&allocating.writer, string); 47 | 48 | const written = allocating.written(); 49 | 50 | const out = try allocator.alloc(u8, written.len + 4); 51 | defer allocator.free(out); 52 | 53 | const header: [4]u8 = @bitCast(@as(u32, @intCast(string.len))); 54 | @memcpy(out[0..4], header[0..4]); 55 | @memcpy(out[4..][0..written.len], written[0..]); 56 | 57 | if (is_buffer) 58 | try L.Zpushbuffer(out) 59 | else 60 | try L.pushlstring(out); 61 | 62 | return 1; 63 | } 64 | 65 | pub fn lua_frame_decompress(L: *VM.lua.State) !i32 { 66 | const allocator = luau.getallocator(L); 67 | 68 | const is_buffer = L.typeOf(1) == .Buffer; 69 | 70 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 71 | 72 | if (string.len < 4) 73 | return L.Zerror("invalid header"); 74 | 75 | var decoder = try lz4.Decoder.init(allocator); 76 | defer decoder.deinit(); 77 | 78 | const sizeHint = std.mem.bytesAsSlice(u32, string[0..4])[0]; 79 | 80 | const decompressed = try decoder.decompress(string[4..], sizeHint); 81 | defer allocator.free(decompressed); 82 | 83 | if (decompressed.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 84 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 85 | 86 | if (is_buffer) try L.Zpushbuffer(decompressed) else try L.pushlstring(decompressed); 87 | 88 | return 1; 89 | } 90 | 91 | pub fn lua_compress(L: *VM.lua.State) !i32 { 92 | const allocator = luau.getallocator(L); 93 | 94 | const is_buffer = L.typeOf(1) == .Buffer; 95 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 96 | 97 | const compressed = try lz4.Standard.compress(allocator, string); 98 | defer allocator.free(compressed); 99 | 100 | if (is_buffer) try L.Zpushbuffer(compressed) else try L.pushlstring(compressed); 101 | 102 | return 1; 103 | } 104 | 105 | pub fn lua_decompress(L: *VM.lua.State) !i32 { 106 | const allocator = luau.getallocator(L); 107 | 108 | const is_buffer = L.typeOf(1) == .Buffer; 109 | 110 | const string = if (is_buffer) L.Lcheckbuffer(1) else L.Lcheckstring(1); 111 | const sizeHint = try L.Zcheckvalue(i32, 2, null); 112 | 113 | const decompressed = try lz4.Standard.decompress(allocator, string, @intCast(sizeHint)); 114 | defer allocator.free(decompressed); 115 | 116 | if (decompressed.len > Zune.Utils.LuaHelper.MAX_LUAU_SIZE) 117 | return L.Zerror("decompressed data exceeds maximum string/buffer size"); 118 | 119 | if (is_buffer) try L.Zpushbuffer(decompressed) else try L.pushlstring(decompressed); 120 | 121 | return 1; 122 | } 123 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | prep: 11 | name: Prepare 12 | runs-on: ubuntu-latest 13 | outputs: 14 | version: ${{ steps.version.outputs.version }} 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Zig 20 | uses: mlugg/setup-zig@v2 21 | with: 22 | version: 0.15.2 23 | use-cache: false 24 | 25 | - name: Get Zune Version 26 | id: version 27 | run: | 28 | version=$(zig build version -Drelease-ver) 29 | echo "version=$version">> $GITHUB_OUTPUT 30 | 31 | build: 32 | needs: prep 33 | strategy: 34 | matrix: 35 | include: 36 | - name: Linux x86_64 37 | target: x86_64-linux-gnu 38 | artifact: zune-${{ needs.prep.outputs.version }}-linux-x86_64 39 | - name: Linux Arm64 40 | target: aarch64-linux-gnu 41 | artifact: zune-${{ needs.prep.outputs.version }}-linux-aarch64 42 | - name: Linux Risc-v64 43 | target: riscv64-linux 44 | artifact: zune-${{ needs.prep.outputs.version }}-linux-riscv64 45 | - name: Linux PowerPC64LE 46 | target: powerpc64le-linux 47 | artifact: zune-${{ needs.prep.outputs.version }}-linux-powerpc64le 48 | - name: Windows x86_64 49 | target: x86_64-windows 50 | artifact: zune-${{ needs.prep.outputs.version }}-windows-x86_64 51 | ext: .exe 52 | - name: Windows Arm64 53 | target: aarch64-windows 54 | artifact: zune-${{ needs.prep.outputs.version }}-windows-aarch64 55 | ext: .exe 56 | - name: macOS x86_64 57 | target: x86_64-macos 58 | artifact: zune-${{ needs.prep.outputs.version }}-macos-x86_64 59 | - name: macOS Arm64 60 | target: aarch64-macos 61 | artifact: zune-${{ needs.prep.outputs.version }}-macos-aarch64 62 | - name: FreeBSD x86_64 63 | target: x86_64-freebsd 64 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-x86_64 65 | - name: FreeBSD Arm64 66 | target: aarch64-freebsd 67 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-aarch64 68 | - name: FreeBSD Risc-v64 69 | target: riscv64-freebsd 70 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-riscv64 71 | - name: FreeBSD PowerPC64LE 72 | target: powerpc64le-freebsd 73 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-powerpc64le 74 | 75 | name: ${{ matrix.name }} 76 | timeout-minutes: 20 77 | runs-on: ubuntu-latest 78 | steps: 79 | - name: Checkout Repository 80 | uses: actions/checkout@v4 81 | 82 | - name: Setup Zig 83 | uses: mlugg/setup-zig@v2 84 | with: 85 | version: 0.15.2 86 | use-cache: false 87 | 88 | - name: Build 89 | run: zig build -Doptimize=ReleaseFast -Drelease-ver -Dtarget=${{ matrix.target }} 90 | 91 | - name: Pack Artifact 92 | run: cd ./zig-out/bin && zip ../../${{ matrix.artifact }}.zip ./zune${{ matrix.ext }} 93 | 94 | - name: Upload Artifact 95 | uses: actions/upload-artifact@v4 96 | with: 97 | name: ${{ matrix.artifact }} 98 | path: ${{ matrix.artifact }}.zip 99 | 100 | release: 101 | name: Release 102 | runs-on: ubuntu-latest 103 | needs: [prep, build] 104 | steps: 105 | - name: Checkout Repository 106 | uses: actions/checkout@v4 107 | 108 | - name: Download Artifacts 109 | uses: actions/download-artifact@v4 110 | with: 111 | path: ./artifacts 112 | merge-multiple: true 113 | 114 | - name: Create Release 115 | uses: softprops/action-gh-release@v2 116 | env: 117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 118 | with: 119 | draft: true 120 | files: ./artifacts/*.zip 121 | name: ${{ needs.prep.outputs.version }} 122 | tag_name: v${{ needs.prep.outputs.version }} 123 | fail_on_unmatched_files: true 124 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yaml: -------------------------------------------------------------------------------- 1 | name: Pre-release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | prep: 11 | name: Prepare 12 | runs-on: ubuntu-latest 13 | outputs: 14 | version: ${{ steps.version.outputs.version }} 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Zig 20 | uses: mlugg/setup-zig@v2 21 | with: 22 | version: 0.15.2 23 | use-cache: false 24 | 25 | - name: Get Zune Version 26 | id: version 27 | run: | 28 | version=$(zig build version) 29 | echo "version=$version">> $GITHUB_OUTPUT 30 | 31 | build: 32 | needs: prep 33 | strategy: 34 | matrix: 35 | include: 36 | - name: Linux x86_64 37 | target: x86_64-linux-gnu 38 | artifact: zune-${{ needs.prep.outputs.version }}-linux-x86_64 39 | - name: Linux Arm64 40 | target: aarch64-linux-gnu 41 | artifact: zune-${{ needs.prep.outputs.version }}-linux-aarch64 42 | - name: Linux Risc-v64 43 | target: riscv64-linux 44 | artifact: zune-${{ needs.prep.outputs.version }}-linux-riscv64 45 | - name: Linux PowerPC64LE 46 | target: powerpc64le-linux 47 | artifact: zune-${{ needs.prep.outputs.version }}-linux-powerpc64le 48 | - name: Windows x86_64 49 | target: x86_64-windows 50 | artifact: zune-${{ needs.prep.outputs.version }}-windows-x86_64 51 | ext: .exe 52 | - name: Windows Arm64 53 | target: aarch64-windows 54 | artifact: zune-${{ needs.prep.outputs.version }}-windows-aarch64 55 | ext: .exe 56 | - name: macOS x86_64 57 | target: x86_64-macos 58 | artifact: zune-${{ needs.prep.outputs.version }}-macos-x86_64 59 | - name: macOS Arm64 60 | target: aarch64-macos 61 | artifact: zune-${{ needs.prep.outputs.version }}-macos-aarch64 62 | - name: FreeBSD x86_64 63 | target: x86_64-freebsd 64 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-x86_64 65 | - name: FreeBSD Arm64 66 | target: aarch64-freebsd 67 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-aarch64 68 | - name: FreeBSD Risc-v64 69 | target: riscv64-freebsd 70 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-riscv64 71 | - name: FreeBSD PowerPC64LE 72 | target: powerpc64le-freebsd 73 | artifact: zune-${{ needs.prep.outputs.version }}-freebsd-powerpc64le 74 | 75 | name: ${{ matrix.name }} 76 | timeout-minutes: 20 77 | runs-on: ubuntu-latest 78 | steps: 79 | - name: Checkout Repository 80 | uses: actions/checkout@v4 81 | 82 | - name: Setup Zig 83 | uses: mlugg/setup-zig@v2 84 | with: 85 | version: 0.15.2 86 | use-cache: false 87 | 88 | - name: Build 89 | run: zig build -Doptimize=ReleaseFast -Dtarget=${{ matrix.target }} 90 | 91 | - name: Pack Artifact 92 | run: cd ./zig-out/bin && zip ../../${{ matrix.artifact }}.zip ./zune${{ matrix.ext }} 93 | 94 | - name: Upload Artifact 95 | uses: actions/upload-artifact@v4 96 | with: 97 | name: ${{ matrix.artifact }} 98 | path: ${{ matrix.artifact }}.zip 99 | 100 | release: 101 | name: Release 102 | runs-on: ubuntu-latest 103 | needs: [prep, build] 104 | steps: 105 | - name: Checkout Repository 106 | uses: actions/checkout@v4 107 | 108 | - name: Download Artifacts 109 | uses: actions/download-artifact@v4 110 | with: 111 | path: ./artifacts 112 | merge-multiple: true 113 | 114 | - name: Create Release 115 | uses: softprops/action-gh-release@v2 116 | env: 117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 118 | with: 119 | draft: true 120 | prerelease: true 121 | files: ./artifacts/*.zip 122 | name: ${{ needs.prep.outputs.version }} 123 | tag_name: v${{ needs.prep.outputs.version }} 124 | fail_on_unmatched_files: true 125 | -------------------------------------------------------------------------------- /src/commands/init.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const command = @import("lib.zig"); 4 | 5 | const INIT_CONFIG_FILE = 6 | \\# Zune Configuration 7 | \\ 8 | \\# Debug settings 9 | \\# This affects the way Zune handles errors and debug information. 10 | \\[runtime.debug] 11 | \\detailed_error = true 12 | \\ 13 | \\# Compiling settings 14 | \\# This affects all required files and the main file. 15 | \\[runtime.luau.options] 16 | \\debug_level = 2 17 | \\optimization_level = 1 18 | \\native_code_gen = true 19 | \\ 20 | \\# Formatter settings 21 | \\# This affects the output of the formatter while printing. 22 | \\[resolvers.formatter] 23 | \\max_depth = 4 24 | \\use_color = true 25 | \\table_address = true 26 | \\recursive_Table = false 27 | \\buffer_max_display = 48 28 | \\ 29 | \\# FFlag settings for Luau 30 | \\# You can use `zune luau list-fflags` to list all available FFlags. 31 | \\#[runtime.luau.fflags] 32 | \\#DebugCodegenOptSize = false 33 | \\ 34 | \\# Features settings 35 | \\# This affects the features that are enabled in Zune. 36 | \\# mainly builtin libraries and features. 37 | \\#[features.builtin] 38 | \\#fs = true 39 | \\#io = true 40 | \\#net = true 41 | \\#process = true 42 | \\#task = true 43 | \\#luau = true 44 | \\#serde = true 45 | \\#crypto = true 46 | \\#datetime = true 47 | \\#regex = true 48 | \\#sqlite = true 49 | \\#require = true 50 | \\#random = true 51 | \\#thread = true 52 | \\#ffi = true 53 | ; 54 | 55 | fn Execute(_: std.mem.Allocator, args: []const []const u8) !void { 56 | const config_file = std.fs.cwd().createFile("zune.toml", .{ 57 | .exclusive = true, 58 | }) catch |err| switch (err) { 59 | error.PathAlreadyExists => { 60 | std.debug.print("zune configuration file, 'zune.toml' already exists.\n", .{}); 61 | return; 62 | }, 63 | else => return err, 64 | }; 65 | defer config_file.close(); 66 | 67 | try config_file.writeAll(INIT_CONFIG_FILE); 68 | 69 | if (args.len > 0 and std.mem.eql(u8, args[0], "bare")) 70 | return; 71 | 72 | try std.fs.cwd().makePath("src"); 73 | 74 | if (args.len > 0 and std.mem.eql(u8, args[0], "module")) { 75 | try std.fs.cwd().makePath("tests"); 76 | blk: { 77 | const file = std.fs.cwd().createFile("src/init.luau", .{ 78 | .exclusive = true, 79 | }) catch |err| switch (err) { 80 | error.PathAlreadyExists => break :blk, 81 | else => return err, 82 | }; 83 | defer file.close(); 84 | 85 | try file.writeAll( 86 | \\local io = zune.io 87 | \\ 88 | \\local module = {} 89 | \\ 90 | \\function module.hello() 91 | \\ io.stdout:write("Hello, World!\n") 92 | \\end 93 | \\ 94 | \\return module 95 | ); 96 | } 97 | blk: { 98 | const file = std.fs.cwd().createFile("tests/init.luau", .{ 99 | .exclusive = true, 100 | }) catch |err| switch (err) { 101 | error.PathAlreadyExists => break :blk, 102 | else => return err, 103 | }; 104 | defer file.close(); 105 | 106 | try file.writeAll( 107 | \\local testing = zune.testing 108 | \\ 109 | \\local test = testing.test 110 | \\ 111 | \\local module = require("./src") 112 | \\ 113 | \\test("module.hello", function() 114 | \\ module.hello() 115 | \\end) 116 | ); 117 | } 118 | } else { 119 | const file = std.fs.cwd().createFile("src/main.luau", .{ 120 | .exclusive = true, 121 | }) catch |err| switch (err) { 122 | error.PathAlreadyExists => return, 123 | else => return err, 124 | }; 125 | defer file.close(); 126 | 127 | try file.writeAll( 128 | \\local io = zune.io 129 | \\ 130 | \\local function main() 131 | \\ io.stdout:write("Hello, World!") 132 | \\end 133 | \\ 134 | \\main() 135 | \\ 136 | ); 137 | } 138 | } 139 | 140 | pub const Command = command.Command{ 141 | .name = "init", 142 | .execute = Execute, 143 | }; 144 | -------------------------------------------------------------------------------- /src/core/utils/print.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Zune = @import("zune"); 4 | 5 | const ColorMap = std.StaticStringMap([]const u8).initComptime(.{ 6 | .{ "red", "31" }, 7 | .{ "green", "32" }, 8 | .{ "yellow", "33" }, 9 | .{ "blue", "34" }, 10 | .{ "magenta", "35" }, 11 | .{ "cyan", "36" }, 12 | .{ "white", "37" }, 13 | .{ "black", "30" }, 14 | 15 | .{ "bblack", "90" }, 16 | .{ "bred", "91" }, 17 | .{ "bgreen", "92" }, 18 | .{ "byellow", "93" }, 19 | .{ "bblue", "94" }, 20 | .{ "bmagenta", "95" }, 21 | .{ "bcyan", "96" }, 22 | .{ "bwhite", "97" }, 23 | 24 | .{ "bold", "1" }, 25 | .{ "dim", "2" }, 26 | .{ "italic", "3" }, 27 | .{ "underline", "4" }, 28 | .{ "blink", "5" }, 29 | .{ "reverse", "7" }, 30 | .{ "clear", "0" }, 31 | }); 32 | 33 | fn ColorFormat(comptime fmt: []const u8, comptime use_colors: bool) []const u8 { 34 | comptime var new_fmt: []const u8 = ""; 35 | 36 | comptime var start = -1; 37 | comptime var ignore_next = false; 38 | comptime var closed = true; 39 | @setEvalBranchQuota(200_000); 40 | comptime for (fmt, 0..) |c, i| switch (c) { 41 | '<' => { 42 | if (ignore_next) { 43 | if (!closed) { 44 | if (use_colors) 45 | new_fmt = new_fmt ++ &[_]u8{'m'}; 46 | closed = true; 47 | } 48 | new_fmt = new_fmt ++ &[_]u8{c}; 49 | ignore_next = false; 50 | continue; 51 | } 52 | if (i + 1 < fmt.len and fmt[i + 1] == '<') { 53 | ignore_next = true; 54 | continue; 55 | } 56 | if (use_colors) { 57 | if (!closed) 58 | new_fmt = new_fmt ++ &[_]u8{';'} 59 | else 60 | new_fmt = new_fmt ++ "\x1b["; 61 | } 62 | if (start >= 0) 63 | @compileError("Nested color tags in format string: " ++ fmt); 64 | closed = false; 65 | start = i; 66 | }, 67 | '>' => { 68 | if (ignore_next) { 69 | if (!closed) { 70 | if (use_colors) 71 | new_fmt = new_fmt ++ &[_]u8{'m'}; 72 | closed = true; 73 | } 74 | new_fmt = new_fmt ++ &[_]u8{c}; 75 | ignore_next = false; 76 | continue; 77 | } 78 | if (i + 1 < fmt.len and fmt[i + 1] == '>') { 79 | ignore_next = true; 80 | continue; 81 | } 82 | if (start >= 0) { 83 | const color_name = fmt[start + 1 .. i]; 84 | const code = ColorMap.get(color_name) orelse @compileError("unknown color: " ++ color_name); 85 | if (use_colors) 86 | new_fmt = new_fmt ++ code; 87 | start = -1; 88 | } else @compileError("Unmatched closing color tag in format string: " ++ fmt); 89 | }, 90 | else => if (start < 0) { 91 | if (!closed) { 92 | if (use_colors) 93 | new_fmt = new_fmt ++ &[_]u8{'m'}; 94 | closed = true; 95 | } 96 | new_fmt = new_fmt ++ &[_]u8{c}; 97 | }, 98 | }; 99 | if (!closed) { 100 | if (use_colors) 101 | new_fmt = new_fmt ++ &[_]u8{'m'}; 102 | } 103 | if (start >= 0) 104 | @compileError("Unclosed color tag in format string: " ++ fmt); 105 | return new_fmt; 106 | } 107 | 108 | pub fn print(comptime fmt: []const u8, args: anytype) void { 109 | var buffer: [64]u8 = undefined; 110 | const bw = std.debug.lockStderrWriter(&buffer); 111 | defer std.debug.unlockStderrWriter(); 112 | 113 | if (Zune.STATE.FORMAT.USE_COLOR == true and std.mem.eql(u8, Zune.STATE.ENV_MAP.get("NO_COLOR") orelse "0", "0")) { 114 | const color_format = comptime ColorFormat(fmt, true); 115 | nosuspend bw.print(color_format, args) catch return; 116 | } else { 117 | const color_format = comptime ColorFormat(fmt, false); 118 | nosuspend bw.print(color_format, args) catch return; 119 | } 120 | } 121 | 122 | pub fn writerPrint(writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype) !void { 123 | if (Zune.STATE.FORMAT.USE_COLOR == true and std.mem.eql(u8, Zune.STATE.ENV_MAP.get("NO_COLOR") orelse "0", "0")) { 124 | const color_format = comptime ColorFormat(fmt, true); 125 | try writer.print(color_format, args); 126 | } else { 127 | const color_format = comptime ColorFormat(fmt, false); 128 | try writer.print(color_format, args); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/core/standard/serde/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | 4 | const Zune = @import("zune"); 5 | 6 | const LuaHelper = Zune.Utils.LuaHelper; 7 | 8 | const json = @import("json.zig"); 9 | const toml = @import("toml.zig"); 10 | const yaml = @import("yaml.zig"); 11 | const base64 = @import("base64.zig"); 12 | 13 | const gzip = @import("gzip.zig"); 14 | const zlib = @import("zlib.zig"); 15 | const flate = @import("flate.zig"); 16 | const lz4 = @import("lz4.zig"); 17 | const brotli = @import("brotli.zig"); 18 | const zstd = @import("zstd.zig"); 19 | 20 | const VM = luau.VM; 21 | 22 | pub const LIB_NAME = "serde"; 23 | 24 | pub fn loadLib(L: *VM.lua.State) !void { 25 | try L.createtable(0, 10); 26 | 27 | { // Json 28 | try L.createtable(0, 4); 29 | 30 | try L.Zsetfieldfn(-1, "encode", json.LuaEncoder(.JSON)); 31 | try L.Zsetfieldfn(-1, "decode", json.LuaDecoder(.JSON)); 32 | 33 | try json.lua_setprops(L); 34 | 35 | L.setreadonly(-1, true); 36 | try L.rawsetfield(-2, "json"); 37 | } 38 | 39 | { // Json5 40 | try L.createtable(0, 4); 41 | 42 | try L.Zsetfieldfn(-1, "encode", json.LuaEncoder(.JSON5)); 43 | try L.Zsetfieldfn(-1, "decode", json.LuaDecoder(.JSON5)); 44 | 45 | _ = L.rawgetfield(-2, "json"); 46 | 47 | _ = L.rawgetfield(-1, "indents"); 48 | try L.rawsetfield(-3, "indents"); 49 | 50 | _ = L.rawgetfield(-1, "values"); 51 | try L.rawsetfield(-3, "values"); 52 | 53 | L.pop(1); 54 | 55 | L.setreadonly(-1, true); 56 | try L.rawsetfield(-2, "json5"); 57 | } 58 | 59 | { // Toml 60 | try L.Zpushvalue(.{ 61 | .encode = toml.lua_encode, 62 | .decode = toml.lua_decode, 63 | }); 64 | L.setreadonly(-1, true); 65 | 66 | try L.rawsetfield(-2, "toml"); 67 | } 68 | 69 | { // Yaml 70 | try L.Zpushvalue(.{ 71 | .encode = yaml.lua_encode, 72 | .decode = yaml.lua_decode, 73 | }); 74 | L.setreadonly(-1, true); 75 | 76 | try L.rawsetfield(-2, "yaml"); 77 | } 78 | 79 | { // Base64 80 | try L.Zpushvalue(.{ 81 | .encode = base64.lua_encode, 82 | .decode = base64.lua_decode, 83 | }); 84 | L.setreadonly(-1, true); 85 | 86 | try L.rawsetfield(-2, "base64"); 87 | } 88 | 89 | { // Gzip 90 | try L.Zpushvalue(.{ 91 | .compress = gzip.lua_compress, 92 | .decompress = gzip.lua_decompress, 93 | }); 94 | L.setreadonly(-1, true); 95 | 96 | try L.rawsetfield(-2, "gzip"); 97 | } 98 | 99 | { // Zlib 100 | try L.Zpushvalue(.{ 101 | .compress = zlib.lua_compress, 102 | .decompress = zlib.lua_decompress, 103 | }); 104 | L.setreadonly(-1, true); 105 | 106 | try L.rawsetfield(-2, "zlib"); 107 | } 108 | 109 | { // Flate 110 | try L.Zpushvalue(.{ 111 | .compress = flate.lua_compress, 112 | .decompress = flate.lua_decompress, 113 | }); 114 | L.setreadonly(-1, true); 115 | 116 | try L.rawsetfield(-2, "flate"); 117 | } 118 | 119 | { // Lz4 120 | try L.Zpushvalue(.{ 121 | .compress = lz4.lua_compress, 122 | .compressFrame = lz4.lua_frame_compress, 123 | .decompress = lz4.lua_decompress, 124 | .decompressFrame = lz4.lua_frame_decompress, 125 | }); 126 | L.setreadonly(-1, true); 127 | 128 | try L.rawsetfield(-2, "lz4"); 129 | } 130 | 131 | { // Zstd 132 | try L.Zpushvalue(.{ 133 | .compress = zstd.lua_compress, 134 | .decompress = zstd.lua_decompress, 135 | }); 136 | L.setreadonly(-1, true); 137 | 138 | try L.rawsetfield(-2, "zstd"); 139 | } 140 | 141 | { // Brotli 142 | try L.Zpushvalue(.{ 143 | .compress = brotli.lua_compress, 144 | .decompress = brotli.lua_decompress, 145 | }); 146 | L.setreadonly(-1, true); 147 | 148 | try L.rawsetfield(-2, "brotli"); 149 | } 150 | 151 | L.setreadonly(-1, true); 152 | 153 | try LuaHelper.registerModule(L, LIB_NAME); 154 | } 155 | 156 | test { 157 | _ = json; 158 | _ = toml; 159 | _ = yaml; 160 | _ = base64; 161 | _ = gzip; 162 | _ = zlib; 163 | _ = flate; 164 | _ = lz4; 165 | _ = zstd; 166 | } 167 | 168 | test "serde" { 169 | const TestRunner = @import("../../utils/testrunner.zig"); 170 | 171 | const testResult = try TestRunner.runTest( 172 | TestRunner.newTestFile("standard/serde/init.luau"), 173 | &.{}, 174 | .{}, 175 | ); 176 | 177 | try std.testing.expect(testResult.failed == 0); 178 | try std.testing.expect(testResult.total > 0); 179 | } 180 | -------------------------------------------------------------------------------- /src/core/runtime/profiler.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const luau = @import("luau"); 3 | const builtin = @import("builtin"); 4 | 5 | const VM = luau.VM; 6 | 7 | var active = false; 8 | 9 | var ticks: u64 = 0; 10 | var currentTicks: u64 = 0; 11 | 12 | var samples: u64 = 0; 13 | var frequency: u64 = 10000; 14 | 15 | var gcstats: [16]u64 = [_]u64{0} ** 16; 16 | 17 | var callbacks: ?*VM.lua.Callbacks = null; 18 | 19 | var STACK: std.ArrayList(u8) = .empty; 20 | var DATA: std.StringHashMap(u64) = .init(std.heap.page_allocator); 21 | 22 | var thread: ?std.Thread = null; 23 | 24 | // This code is based on https://github.com/luau-lang/luau/blob/946a097e93fda5df23c9afaf29b101e168a03bd5/CLI/Profiler.cpp 25 | fn interrupt(lua_state: *VM.lua.State, gc: c_int) !void { 26 | const L: *VM.lua.State = lua_state; 27 | 28 | const currTicks = ticks; 29 | const elapsedTicks = currTicks - currentTicks; 30 | 31 | if (elapsedTicks > 0) { 32 | STACK.clearRetainingCapacity(); 33 | 34 | if (gc > 0) 35 | try STACK.appendSlice(std.heap.page_allocator, "GC,GC,"); 36 | 37 | var level: i32 = 0; 38 | var ar: VM.lua.Debug = .{ .ssbuf = undefined }; 39 | while (L.getinfo(level, "sn", &ar)) : (level += 1) { 40 | if (STACK.items.len > 0) 41 | try STACK.append(std.heap.page_allocator, ';'); 42 | 43 | try STACK.appendSlice(std.heap.page_allocator, ar.short_src.?); 44 | try STACK.append(std.heap.page_allocator, ','); 45 | 46 | if (ar.name) |name| 47 | try STACK.appendSlice(std.heap.page_allocator, name); 48 | 49 | try STACK.append(std.heap.page_allocator, ','); 50 | 51 | var buf: [48]u8 = undefined; 52 | const slice = std.fmt.bufPrint(&buf, "{d}", .{ar.linedefined.?}) catch unreachable; 53 | try STACK.appendSlice(std.heap.page_allocator, slice); 54 | } 55 | 56 | if (STACK.items.len > 0) { 57 | if (!DATA.contains(STACK.items)) 58 | try DATA.put(try std.heap.page_allocator.dupe(u8, STACK.items), elapsedTicks) 59 | else { 60 | const entry = DATA.getEntry(STACK.items) orelse std.debug.panic("[Profiler] entry key not found", .{}); 61 | entry.value_ptr.* += elapsedTicks; 62 | } 63 | } 64 | 65 | if (gc > 0) 66 | gcstats[@intCast(gc)] += elapsedTicks; 67 | } 68 | 69 | currentTicks = currTicks; 70 | if (callbacks) |cb| 71 | cb.*.interrupt = null; 72 | } 73 | 74 | fn lua_interrupt(lua_state: ?*VM.lua.State, gc: c_int) callconv(.c) void { 75 | const L: *VM.lua.State = @ptrCast(lua_state.?); 76 | interrupt(L, gc) catch |err| std.debug.panic("{}", .{err}); 77 | } 78 | 79 | fn loop() void { 80 | var last = VM.lperf.clock(); 81 | while (active) { 82 | const now = VM.lperf.clock(); 83 | if (now - last >= 1.0 / @as(f64, @floatFromInt(frequency))) { 84 | const lticks: u64 = @intFromFloat((now - last) * 1e6); 85 | 86 | ticks += lticks; 87 | samples += 1; 88 | if (callbacks) |cb| 89 | cb.*.interrupt = lua_interrupt; 90 | 91 | last += @as(f64, @floatFromInt(ticks)) * 1e-6; 92 | } else { 93 | std.Thread.yield() catch |err| std.debug.print("[Profiler] Failed to yield thread: {}", .{err}); 94 | } 95 | } 96 | } 97 | 98 | pub fn start(L: *VM.lua.State, freq: u64) !void { 99 | if (comptime builtin.single_threaded) 100 | return error.UnsupportedPlatform; 101 | const allocator = luau.getallocator(L); 102 | 103 | active = true; 104 | frequency = freq; 105 | callbacks = L.callbacks(); 106 | 107 | thread = try std.Thread.spawn(.{ .allocator = allocator }, loop, .{}); 108 | } 109 | 110 | pub fn end() void { 111 | active = false; 112 | if (thread) |t| 113 | t.join(); 114 | STACK.deinit(std.heap.page_allocator); 115 | } 116 | 117 | pub fn dump(path: []const u8) void { 118 | const data_size = DATA.count(); 119 | var total: u64 = 0; 120 | { 121 | const file = std.fs.cwd().createFile(path, .{}) catch |err| std.debug.panic("[Profiler] Failed to create file: {}", .{err}); 122 | defer file.close(); 123 | 124 | var buffer: [1024]u8 = undefined; 125 | var file_writer = file.writer(&buffer); 126 | 127 | const writer = &file_writer.interface; 128 | 129 | var data_iter = DATA.iterator(); 130 | while (data_iter.next()) |entry| { 131 | writer.print("{d} {s}\n", .{ entry.value_ptr.*, entry.key_ptr.* }) catch |err| std.debug.panic("[Profiler] Failed to write into file: {}", .{err}); 132 | total += entry.value_ptr.*; 133 | std.heap.page_allocator.free(entry.key_ptr.*); 134 | } 135 | writer.flush() catch |err| std.debug.panic("[Profiler] Failed to flush file: {}", .{err}); 136 | DATA.deinit(); 137 | } 138 | std.debug.print("[Profiler] dump written to {s} (total runtime {d:.3} seconds, {d} samples, {d} stacks)\n", .{ 139 | path, 140 | @as(f64, @floatFromInt(total)) / 1e6, 141 | samples, 142 | data_size, 143 | }); 144 | } 145 | -------------------------------------------------------------------------------- /test/standard/crypto/aead/isap.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local crypto = zune.crypto; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | local aead = crypto.aead; 10 | 11 | local isap = aead.isap; 12 | 13 | type Input = buffer | string; 14 | local function getsize(input: Input): number 15 | return if (type(input) == "buffer") then buffer.len(input) else #input; 16 | end 17 | local function examine(algo: any, key: Input, nonce: Input, tests: {{data: Input, ad: Input?, cipher: string, tag: string}}) 18 | for _, test in tests do 19 | expect(function() 20 | algo.encrypt(test.data, "", nonce, test.ad); 21 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 22 | expect(function() 23 | algo.encrypt(test.data, buffer.create(0), nonce, test.ad); 24 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 25 | expect(function() 26 | algo.encrypt(test.data, key, "", test.ad); 27 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 28 | expect(function() 29 | algo.encrypt(test.data, key, buffer.create(0), test.ad); 30 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 31 | local encrypted = algo.encrypt(test.data, key, nonce, test.ad); 32 | expect(encrypted).toBe(expect.similar({ 33 | cipher = expect.type("buffer"), 34 | tag = expect.type("buffer"), 35 | })); 36 | expect({ 37 | cipher = buffer.tostring(encrypted.cipher), 38 | tag = buffer.tostring(encrypted.tag), 39 | }).toBe(expect.similar({ 40 | cipher = test.cipher :: string, 41 | tag = test.tag :: string, 42 | })); 43 | expect(algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce, test.ad)).toBe(test.data); 44 | if (test.ad) then 45 | expect(function() 46 | algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce); 47 | end).toThrow("AuthenticationFailed"); 48 | else 49 | expect(function() 50 | algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce, "\0\0\0\0"); 51 | end).toThrow("AuthenticationFailed"); 52 | end 53 | expect(function() 54 | algo.decrypt(encrypted.cipher, "", key, nonce, "\0\0\0\0"); 55 | end).toThrow(`invalid tag length (expected size {getsize(encrypted.tag)})`); 56 | expect(function() 57 | algo.decrypt(encrypted.cipher, buffer.create(0), key, nonce, "\0\0\0\0"); 58 | end).toThrow(`invalid tag length (expected size {getsize(encrypted.tag)})`); 59 | expect(function() 60 | algo.decrypt(encrypted.cipher, encrypted.tag, "", nonce, "\0\0\0\0"); 61 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 62 | expect(function() 63 | algo.decrypt(encrypted.cipher, encrypted.tag, buffer.create(0), nonce, "\0\0\0\0"); 64 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 65 | expect(function() 66 | algo.decrypt(encrypted.cipher, encrypted.tag, key, "", "\0\0\0\0"); 67 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 68 | expect(function() 69 | algo.decrypt(encrypted.cipher, encrypted.tag, key, buffer.create(0), "\0\0\0\0"); 70 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 71 | end 72 | end 73 | 74 | describe("isap", function() 75 | describe("IsapA128A", function() 76 | test("Normal", function() 77 | examine(isap.IsapA128A, "abcdefghijklmnop", "1234567890121234", { 78 | { data = "zune+luau", ad = nil, cipher = "|\x19oh\xC0\xC9hY\xE6", tag = "\xEF\x9D\x17\xE6\xA4\xBD\xF3)\xF4\x8B\xFC}\x8Cy\x1Ds" }, 79 | }); 80 | examine(isap.IsapA128A, "0000000000000000", "0000000000000000", { 81 | { data = "runtime", ad = nil, cipher = "\x95d\x86\xA2y\xFA\xA3", tag = "*\xC4\x94\xCD\xE4\x98\x99\x9D\xE3\xB1\xCE\x0F\x9D\x95\xC0\xC4" }, 82 | }); 83 | end) 84 | test("With Associated Data", function() 85 | examine(isap.IsapA128A, "abcdefghijklmnop", "1234567890121234", { 86 | { data = "zune+luau", ad = "Some Associated Data", cipher = "|\x19oh\xC0\xC9hY\xE6", tag = "\f\xBD\xDA\xE3 \x91\xA3\x15\x12\xD3\xACj\xE8\x89X#" }, 87 | }); 88 | examine(isap.IsapA128A, "0000000000000000", "0000000000000000", { 89 | { data = "runtime", ad = "Some Associated Data", cipher = "\x95d\x86\xA2y\xFA\xA3", tag = "\xE0\x9F\x85\xD0\xE51\xA2\xF3\xFB\x19\x16S{j\xD5\x19" }, 90 | }); 91 | end) 92 | test("Buffers", function() 93 | local key = buffer.create(16); 94 | local nonce = buffer.create(16); 95 | examine(isap.IsapA128A, key, nonce, { 96 | { data = "zune+luau", ad = nil, cipher ="\x83`\x93vd\xD9\xF57\x82", tag = "\x25\x03\xA7\xF2\xBC<\x1D\xB1lgon\t5\xEB`" }, 97 | }); 98 | end) 99 | end) 100 | end) 101 | 102 | return nil; 103 | -------------------------------------------------------------------------------- /test/standard/crypto/aead/salsa_poly.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local crypto = zune.crypto; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | local aead = crypto.aead; 10 | 11 | local salsa_poly = aead.salsa_poly; 12 | 13 | type Input = buffer | string; 14 | local function getsize(input: Input): number 15 | return if (type(input) == "buffer") then buffer.len(input) else #input; 16 | end 17 | local function examine(algo: any, key: Input, nonce: Input, tests: {{data: Input, ad: Input?, cipher: string, tag: string}}) 18 | for _, test in tests do 19 | expect(function() 20 | algo.encrypt(test.data, "", nonce, test.ad); 21 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 22 | expect(function() 23 | algo.encrypt(test.data, buffer.create(0), nonce, test.ad); 24 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 25 | expect(function() 26 | algo.encrypt(test.data, key, "", test.ad); 27 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 28 | expect(function() 29 | algo.encrypt(test.data, key, buffer.create(0), test.ad); 30 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 31 | local encrypted = algo.encrypt(test.data, key, nonce, test.ad); 32 | expect(encrypted).toBe(expect.similar({ 33 | cipher = expect.type("buffer"), 34 | tag = expect.type("buffer"), 35 | })); 36 | expect({ 37 | cipher = buffer.tostring(encrypted.cipher), 38 | tag = buffer.tostring(encrypted.tag), 39 | }).toBe(expect.similar({ 40 | cipher = test.cipher :: string, 41 | tag = test.tag :: string, 42 | })); 43 | expect(algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce, test.ad)).toBe(test.data); 44 | if (test.ad) then 45 | expect(function() 46 | algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce); 47 | end).toThrow("AuthenticationFailed"); 48 | else 49 | expect(function() 50 | algo.decrypt(encrypted.cipher, encrypted.tag, key, nonce, "\0\0\0\0"); 51 | end).toThrow("AuthenticationFailed"); 52 | end 53 | expect(function() 54 | algo.decrypt(encrypted.cipher, "", key, nonce, "\0\0\0\0"); 55 | end).toThrow(`invalid tag length (expected size {getsize(encrypted.tag)})`); 56 | expect(function() 57 | algo.decrypt(encrypted.cipher, buffer.create(0), key, nonce, "\0\0\0\0"); 58 | end).toThrow(`invalid tag length (expected size {getsize(encrypted.tag)})`); 59 | expect(function() 60 | algo.decrypt(encrypted.cipher, encrypted.tag, "", nonce, "\0\0\0\0"); 61 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 62 | expect(function() 63 | algo.decrypt(encrypted.cipher, encrypted.tag, buffer.create(0), nonce, "\0\0\0\0"); 64 | end).toThrow(`invalid key length (expected size {getsize(key)})`); 65 | expect(function() 66 | algo.decrypt(encrypted.cipher, encrypted.tag, key, "", "\0\0\0\0"); 67 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 68 | expect(function() 69 | algo.decrypt(encrypted.cipher, encrypted.tag, key, buffer.create(0), "\0\0\0\0"); 70 | end).toThrow(`invalid nonce length (expected size {getsize(nonce)})`); 71 | end 72 | end 73 | 74 | describe("salsa_poly", function() 75 | describe("XSalsa20Poly1305", function() 76 | test("Normal", function() 77 | examine(salsa_poly.XSalsa20Poly1305, "abcdefghijklmnopqrstuvwxyz_abcde", "123456789012123456789012", { 78 | { data = "zune+luau", ad = nil, cipher = "\x8BK\xC1\xE4\x93\xE2P\xA4\xD6", tag = ">C\x8B\xEBh\x8A^\x97L\x86\f^\xA0q\xA9\x82" }, 79 | }); 80 | examine(salsa_poly.XSalsa20Poly1305, "00000000000000000000000000000000", "000000000000000000000000", { 81 | { data = "runtime", ad = nil, cipher = "\x14-\x95\xC2\xCE\xF0\xCF", tag = "\xA6[a\xA1pT\x03\"\xC5~\x18E\xA4\x00Z\x06" }, 82 | }); 83 | end) 84 | test("With Associated Data", function() 85 | examine(salsa_poly.XSalsa20Poly1305, "abcdefghijklmnopqrstuvwxyz_abcde", "123456789012123456789012", { 86 | { data = "zune+luau", ad = "Some Associated Data", cipher = "\x8BK\xC1\xE4\x93\xE2P\xA4\xD6", tag = "\xE8\xF8\xEC$\xB2O\xE1\x10[M\xEE\xEB\xB2\x15^Z" }, 87 | }); 88 | examine(salsa_poly.XSalsa20Poly1305, "00000000000000000000000000000000", "000000000000000000000000", { 89 | { data = "runtime", ad = "Some Associated Data", cipher = "\x14-\x95\xC2\xCE\xF0\xCF", tag = "\xCF\x8CE\xD6j\xBC>\xB0\xACK|\xC5p9\xF8\xEF" }, 90 | }); 91 | end) 92 | test("Buffers", function() 93 | local key = buffer.create(32); 94 | local nonce = buffer.create(24); 95 | examine(salsa_poly.XSalsa20Poly1305, key, nonce, { 96 | { data = "zune+luau", ad = nil, cipher ="\xBCK\xD5\x9A\xD5\xE9\xBBM\x9E", tag = "\x1B\xB9\xCDeNBh\x16-\x87\x06q\x04\x11\xCC>" }, 97 | }); 98 | end) 99 | end) 100 | end) 101 | 102 | return nil; 103 | -------------------------------------------------------------------------------- /test/standard/serde/lz4.test.luau: -------------------------------------------------------------------------------- 1 | --!strict 2 | local serde = zune.serde; 3 | local testing = zune.testing; 4 | 5 | local describe = testing.describe; 6 | local expect = testing.expect; 7 | local test = testing.test; 8 | 9 | describe("Lz4", function() 10 | local sample = string.rep([[ 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 12 | ]], 20); 13 | describe("Standard", function() 14 | local compressed_sample_default = "\x13 \x01\x00\xF3*Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n@\x00\x01\x02\x00\x0FE\x00\xFF\xFF\xFF\xFF\xFF\x04P\n "; 15 | 16 | describe("Compression", function() 17 | test("Default", function() 18 | expect(serde.lz4.compress(sample)).toBe(compressed_sample_default); 19 | end) 20 | test("Buffer (Default)", function() 21 | local buf = serde.lz4.compress(buffer.fromstring(sample)); 22 | expect(buf).toBe(expect.type("buffer")); 23 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 24 | end) 25 | test("Fail", function() 26 | expect(function() serde.lz4.compress(true) end).toThrow("invalid argument #1 to 'compress' (string expected, got boolean)"); 27 | end) 28 | end) 29 | 30 | describe("Decompression", function() 31 | test("Default", function() 32 | expect(serde.lz4.decompress(compressed_sample_default, #sample)).toBe(sample); 33 | end) 34 | test("Buffer (Default)", function() 35 | local buf = serde.lz4.decompress(buffer.fromstring(compressed_sample_default), #sample); 36 | expect(buf).toBe(expect.type("buffer")); 37 | expect(buffer.tostring(buf)).toBe(sample); 38 | end) 39 | test("Fail", function() 40 | expect(function() serde.lz4.decompress("\x01\x00\x00", "") end).toThrow("invalid argument #2 to 'decompress' (number expected, got string)"); 41 | expect(function() serde.lz4.decompress("\x01\x00\x00", 4) end).toThrow("Failed"); 42 | end) 43 | end) 44 | end) 45 | describe("Frame", function() 46 | local compressed_sample_default = "d\x05\x00\x00\x04\"M\x18d@\xA7P\x00\x00\x00\x13 \x01\x00\xF4*Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nA\x00\x0FE\x00\xFF\xFF\xFF\xFF\xFF\bP\n \x00\x00\x00\x00\xC0rE3"; 47 | local compressed_sample_level0 = "d\x05\x00\x00\x04\"M\x18d@\xA7S\x00\x00\x00\x13 \x01\x00\xF3*Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n@\x00\x01\x02\x00\x0FE\x00\xFF\xFF\xFF\xFF\xFF\x04P\n \x00\x00\x00\x00\xC0rE3"; 48 | 49 | describe("Compression", function() 50 | test("Default", function() 51 | expect(serde.lz4.compressFrame(sample)).toBe(compressed_sample_default); 52 | end) 53 | test("Buffer (Default)", function() 54 | local buf = serde.lz4.compressFrame(buffer.fromstring(sample)); 55 | expect(buf).toBe(expect.type("buffer")); 56 | expect(buffer.tostring(buf)).toBe(compressed_sample_default); 57 | end) 58 | test("Level 0", function() 59 | expect(serde.lz4.compressFrame(sample, {level = 0})).toBe(compressed_sample_level0); 60 | end) 61 | test("Level 1", function() 62 | expect(serde.lz4.compressFrame(sample, {level = 1})).toBe(compressed_sample_level0); 63 | end) 64 | test("Level 10", function() 65 | expect(serde.lz4.compressFrame(sample, {level = 10})).toBe(compressed_sample_default); 66 | end) 67 | test("Level 50", function() 68 | expect(serde.lz4.compressFrame(sample, {level = 50})).toBe(compressed_sample_default); 69 | end) 70 | test("Level 100", function() 71 | expect(serde.lz4.compressFrame(sample, {level = 100})).toBe(compressed_sample_default); 72 | end) 73 | test("Level 1000", function() 74 | expect(serde.lz4.compressFrame(sample, {level = 1000})).toBe(compressed_sample_default); 75 | end) 76 | test("Fail", function() 77 | expect(function() serde.lz4.compressFrame(sample, {level = -1}) end).toThrow("options 'level' must not be less than 0"); 78 | expect(function() serde.lz4.compressFrame(true) end).toThrow("invalid argument #1 to 'compressFrame' (string expected, got boolean)"); 79 | end) 80 | end) 81 | 82 | describe("Decompression", function() 83 | test("Default", function() 84 | expect(serde.lz4.decompressFrame(compressed_sample_default)).toBe(sample); 85 | end) 86 | test("Buffer (Default)", function() 87 | local buf = serde.lz4.decompressFrame(buffer.fromstring(compressed_sample_default)); 88 | expect(buf).toBe(expect.type("buffer")); 89 | expect(buffer.tostring(buf)).toBe(sample); 90 | end) 91 | test("Level 0", function() 92 | expect(serde.lz4.decompressFrame(compressed_sample_level0)).toBe(sample); 93 | end) 94 | test("Fail", function() 95 | expect(function() serde.lz4.decompressFrame("\x01\x00\x00") end).toThrow("invalid header"); 96 | end) 97 | end) 98 | end) 99 | end) 100 | 101 | return nil; 102 | -------------------------------------------------------------------------------- /src/core/standard/platform/memory.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | const posix = std.posix; 5 | const windows = std.os.windows; 6 | 7 | pub const SystemMemoryError = std.process.TotalSystemMemoryError || error{ 8 | UnknownFreeSystemMemory, 9 | }; 10 | 11 | pub const BYTES_PER_MB = 1024 * 1024; 12 | 13 | pub fn getTotalSystemMemory() SystemMemoryError!u64 { 14 | switch (comptime builtin.os.tag) { 15 | .linux => { 16 | var info: std.os.linux.Sysinfo = undefined; 17 | const result: usize = std.os.linux.sysinfo(&info); 18 | if (std.os.linux.E.init(result) != .SUCCESS) { 19 | return error.UnknownTotalSystemMemory; 20 | } 21 | return info.totalram * info.mem_unit; 22 | }, 23 | .freebsd => { 24 | var physmem: c_ulong = undefined; 25 | var len: usize = @sizeOf(c_ulong); 26 | posix.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) { 27 | error.NameTooLong, error.UnknownName => unreachable, 28 | else => return error.UnknownTotalSystemMemory, 29 | }; 30 | return @as(usize, @intCast(physmem)); 31 | }, 32 | // whole Darwin family 33 | .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { 34 | // "hw.memsize" returns uint64_t 35 | var physmem: u64 = undefined; 36 | var len: usize = @sizeOf(u64); 37 | posix.sysctlbynameZ("hw.memsize", &physmem, &len, null, 0) catch |err| switch (err) { 38 | error.PermissionDenied => unreachable, // only when setting values, 39 | error.SystemResources => unreachable, // memory already on the stack 40 | error.UnknownName => unreachable, // constant, known good value 41 | else => return error.UnknownTotalSystemMemory, 42 | }; 43 | return physmem; 44 | }, 45 | .openbsd => { 46 | const mib: [2]c_int = [_]c_int{ 47 | posix.CTL.HW, 48 | posix.HW.PHYSMEM64, 49 | }; 50 | var physmem: i64 = undefined; 51 | var len: usize = @sizeOf(@TypeOf(physmem)); 52 | posix.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) { 53 | error.NameTooLong => unreachable, // constant, known good value 54 | error.PermissionDenied => unreachable, // only when setting values, 55 | error.SystemResources => unreachable, // memory already on the stack 56 | error.UnknownName => unreachable, // constant, known good value 57 | else => return error.UnknownTotalSystemMemory, 58 | }; 59 | std.debug.assert(physmem >= 0); 60 | return @as(u64, @bitCast(physmem)); 61 | }, 62 | .windows => { 63 | var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; 64 | const rc = windows.ntdll.NtQuerySystemInformation( 65 | .SystemBasicInformation, 66 | &sbi, 67 | @sizeOf(windows.SYSTEM_BASIC_INFORMATION), 68 | null, 69 | ); 70 | if (rc != .SUCCESS) { 71 | return error.UnknownTotalSystemMemory; 72 | } 73 | return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize; 74 | }, 75 | else => return error.UnknownTotalSystemMemory, 76 | } 77 | } 78 | 79 | pub fn getFreeMemory() SystemMemoryError!u64 { 80 | switch (comptime builtin.os.tag) { 81 | .linux => { 82 | var info: std.os.linux.Sysinfo = undefined; 83 | const result: usize = std.os.linux.sysinfo(&info); 84 | if (std.os.linux.E.init(result) != .SUCCESS) { 85 | return error.UnknownFreeSystemMemory; 86 | } 87 | return info.freeram * info.mem_unit; 88 | }, 89 | .freebsd => { 90 | var free: c_int = undefined; 91 | var len: usize = @sizeOf(c_int); 92 | posix.sysctlbynameZ("vm.stats.vm.v_free_count", &free, &len, null, 0) catch |err| switch (err) { 93 | error.NameTooLong, error.UnknownName => unreachable, 94 | else => return error.UnknownFreeSystemMemory, 95 | }; 96 | return @as(usize, @intCast(free)) * std.heap.pageSize(); 97 | }, 98 | // whole Darwin family 99 | .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { 100 | const mach = @import("mach.zig"); 101 | var stats: mach.vm_statistics64 = undefined; 102 | var count: c_uint = @sizeOf(mach.vm_statistics64_data_t) / @sizeOf(c_int); 103 | 104 | const ret = mach.KernE.from( 105 | mach.host_statistics64(std.c.mach_host_self(), mach.HOST_VM_INFO64, @ptrCast(&stats), &count), 106 | ); 107 | 108 | if (ret != .SUCCESS) { 109 | return error.UnknownFreeSystemMemory; 110 | } 111 | 112 | return @as(u64, stats.free_count + stats.inactive_count) * std.heap.pageSize(); 113 | }, 114 | .windows => { 115 | var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; 116 | const rc = windows.ntdll.NtQuerySystemInformation( 117 | .SystemBasicInformation, 118 | &sbi, 119 | @sizeOf(windows.SYSTEM_BASIC_INFORMATION), 120 | null, 121 | ); 122 | if (rc != .SUCCESS) { 123 | return error.UnknownFreeSystemMemory; 124 | } 125 | return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize; 126 | }, 127 | else => return error.UnknownFreeSystemMemory, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/core/standard/platform/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const xev = @import("xev").Dynamic; 3 | const luau = @import("luau"); 4 | const builtin = @import("builtin"); 5 | 6 | const Zune = @import("zune"); 7 | 8 | const Engine = Zune.Runtime.Engine; 9 | const Scheduler = Zune.Runtime.Scheduler; 10 | 11 | const LuaHelper = Zune.Utils.LuaHelper; 12 | const MethodMap = Zune.Utils.MethodMap; 13 | const EnumMap = Zune.Utils.EnumMap; 14 | 15 | const VM = luau.VM; 16 | 17 | const memory = @import("memory.zig"); 18 | 19 | pub const LIB_NAME = "platform"; 20 | 21 | fn lua_getPageSize(L: *VM.lua.State) i32 { 22 | L.pushnumber(@floatFromInt(std.heap.pageSize())); 23 | return 1; 24 | } 25 | 26 | fn lua_getTotalMemory(L: *VM.lua.State) !i32 { 27 | const amt = @divTrunc(try memory.getTotalSystemMemory(), memory.BYTES_PER_MB); 28 | L.pushnumber(@floatFromInt(amt)); 29 | return 1; 30 | } 31 | 32 | fn lua_getFreeMemory(L: *VM.lua.State) !i32 { 33 | const amt = @divTrunc(try memory.getFreeMemory(), memory.BYTES_PER_MB); 34 | L.pushnumber(@floatFromInt(amt)); 35 | return 1; 36 | } 37 | 38 | fn lua_getHostName(L: *VM.lua.State) !i32 { 39 | comptime { 40 | if (!builtin.link_libc) 41 | @compileError("libc needed for gethostname"); 42 | } 43 | switch (comptime builtin.os.tag) { 44 | .windows => { 45 | const win = struct { 46 | pub extern "ws2_32" fn GetHostNameW( 47 | name: [*:0]u16, 48 | namelen: i32, 49 | ) callconv(.winapi) i32; 50 | }; 51 | var wtf16_buf: [256:0]std.os.windows.WCHAR = undefined; 52 | const rc = win.GetHostNameW(&wtf16_buf, @sizeOf(@TypeOf(wtf16_buf))); 53 | if (rc != 0) { 54 | return switch (std.os.windows.ws2_32.WSAGetLastError()) { 55 | .WSAEFAULT => unreachable, 56 | .WSANOTINITIALISED => @panic("WSANOTINITIALISED"), 57 | else => error.UnknownHostName, 58 | }; 59 | } 60 | const bwtf16: [*:0]u16 = &wtf16_buf; 61 | const wtf16_name: []const u16 = std.mem.span(bwtf16); 62 | var wtf8_buf: [256 * 2:0]u8 = undefined; 63 | const wtf8_name = wtf8_buf[0..std.unicode.wtf16LeToWtf8(&wtf8_buf, wtf16_name)]; 64 | try L.pushlstring(wtf8_name); 65 | return 1; 66 | }, 67 | else => {}, 68 | } 69 | var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; 70 | const name = try std.posix.gethostname(&buf); 71 | try L.pushlstring(name); 72 | return 1; 73 | } 74 | 75 | fn lua_getKernelVersion(L: *VM.lua.State) !i32 { 76 | switch (comptime builtin.os.tag) { 77 | .windows => { 78 | var info: std.os.windows.RTL_OSVERSIONINFOW = undefined; 79 | info.dwOSVersionInfoSize = @sizeOf(std.os.windows.RTL_OSVERSIONINFOW); 80 | 81 | switch (std.os.windows.ntdll.RtlGetVersion(&info)) { 82 | .SUCCESS => {}, 83 | else => return error.UnknownKernelVersion, 84 | } 85 | try L.pushfstring("Windows {d}.{d}", .{ info.dwMajorVersion, info.dwMinorVersion }); 86 | }, 87 | // whole Darwin family 88 | .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { 89 | var version: [256:0]u8 = undefined; 90 | var len: usize = @sizeOf(@TypeOf(version)); 91 | std.posix.sysctlbynameZ("kern.osrelease", &version, &len, null, 0) catch |err| switch (err) { 92 | else => return error.UnknownKernelVersion, 93 | }; 94 | const b: [*:0]u8 = &version; 95 | const str: []const u8 = std.mem.span(b); 96 | try L.pushfstring("Darwin {s}", .{str}); 97 | }, 98 | .freebsd => { 99 | var version: [256:0]u8 = undefined; 100 | var len: usize = @sizeOf(@TypeOf(version)); 101 | std.posix.sysctlbynameZ("kern.osrelease", &version, &len, null, 0) catch |err| switch (err) { 102 | else => return error.UnknownKernelVersion, 103 | }; 104 | const b: [*:0]u8 = &version; 105 | const str: []const u8 = std.mem.span(b); 106 | try L.pushfstring("FreeBSD {s}", .{str}); 107 | }, 108 | .linux => { 109 | var info: std.c.utsname = undefined; 110 | if (std.c.uname(&info) != 0) { 111 | return error.UnknownKernelVersion; 112 | } 113 | const b: [*:0]u8 = &info.release; 114 | const str: []const u8 = std.mem.span(b); 115 | try L.pushfstring("Linux {s}", .{str}); 116 | }, 117 | else => return error.UnknownKernelVersion, 118 | } 119 | return 1; 120 | } 121 | 122 | pub fn loadLib(L: *VM.lua.State) !void { 123 | try L.Zpushvalue(.{ 124 | .os = @tagName(builtin.os.tag), 125 | .abi = @tagName(builtin.abi), 126 | .cpu = .{ 127 | .arch = @tagName(builtin.cpu.arch), 128 | .endian = @tagName(builtin.cpu.arch.endian()), 129 | }, 130 | .async = @tagName(xev.backend), 131 | .getPageSize = lua_getPageSize, 132 | .getTotalMemory = lua_getTotalMemory, 133 | .getFreeMemory = lua_getFreeMemory, 134 | .getHostName = lua_getHostName, 135 | .getKernelVersion = lua_getKernelVersion, 136 | }); 137 | 138 | L.setreadonly(-1, true); 139 | 140 | try LuaHelper.registerModule(L, LIB_NAME); 141 | } 142 | 143 | test "platform" { 144 | const TestRunner = @import("../../utils/testrunner.zig"); 145 | 146 | const testResult = try TestRunner.runTest( 147 | TestRunner.newTestFile("standard/platform.test.luau"), 148 | &.{}, 149 | .{}, 150 | ); 151 | 152 | try std.testing.expect(testResult.failed == 0); 153 | try std.testing.expect(testResult.total > 0); 154 | } 155 | -------------------------------------------------------------------------------- /src/commands/repl/History.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const History = @This(); 4 | 5 | allocator: std.mem.Allocator, 6 | list: std.ArrayList([]const u8), 7 | temp_buffer: ?[]const u8 = null, 8 | file: ?[]const u8, 9 | position: usize, 10 | enabled: bool = true, 11 | 12 | pub const MAX_HISTORY_SIZE: u16 = 200; 13 | 14 | pub fn init(allocator: std.mem.Allocator, comptime location: []const u8) !History { 15 | var file_path: ?[]const u8 = null; 16 | if (std.process.getEnvVarOwned(allocator, "HOME") catch null) |path| { 17 | file_path = try std.fs.path.resolve(allocator, &.{ path, location }); 18 | } else if (std.process.getEnvVarOwned(allocator, "USERPROFILE") catch null) |path| { 19 | file_path = try std.fs.path.resolve(allocator, &.{ path, location }); 20 | } 21 | var history_data: std.ArrayList([]const u8) = .empty; 22 | errdefer history_data.deinit(allocator); 23 | errdefer for (history_data.items) |data| allocator.free(data); 24 | if (file_path) |path| { 25 | errdefer allocator.free(path); 26 | const file = std.fs.openFileAbsolute(path, .{}) catch |err| { 27 | if (err == error.FileNotFound) { 28 | return .{ 29 | .allocator = allocator, 30 | .file = file_path, 31 | .list = history_data, 32 | .position = history_data.items.len, 33 | }; 34 | } else return err; 35 | }; 36 | var buffer: [1024]u8 = undefined; 37 | var reader = file.reader(&buffer); 38 | 39 | var allocating: std.Io.Writer.Allocating = .init(allocator); 40 | defer allocating.deinit(); 41 | 42 | while (true) { 43 | allocating.clearRetainingCapacity(); 44 | const amt = reader.interface.streamDelimiter(&allocating.writer, '\n') catch |err| { 45 | if (err == error.EndOfStream) 46 | break 47 | else 48 | return err; 49 | }; 50 | if (amt == 0) 51 | break; 52 | reader.interface.toss(1); 53 | const line = allocating.written(); 54 | if (std.mem.trim(u8, line, " \r").len == 0) 55 | continue; 56 | const copy = try allocator.dupe(u8, line); 57 | try history_data.append(allocator, copy); 58 | if (history_data.items.len > MAX_HISTORY_SIZE) 59 | allocator.free(history_data.orderedRemove(1)); 60 | } 61 | } 62 | return .{ 63 | .allocator = allocator, 64 | .file = file_path, 65 | .list = history_data, 66 | .position = history_data.items.len, 67 | }; 68 | } 69 | 70 | pub fn reset(self: *History) void { 71 | self.position = self.list.items.len; 72 | } 73 | 74 | pub fn save(self: *History, line: []const u8) void { 75 | if (std.mem.trim(u8, line, " ").len == 0) 76 | return; 77 | if (self.list.items.len > 0) 78 | if (std.mem.eql(u8, self.list.items[self.list.items.len - 1], line)) 79 | return; 80 | const line_copy = self.allocator.dupe(u8, line) catch return; 81 | self.list.append(self.allocator, line_copy) catch { 82 | self.allocator.free(line_copy); 83 | return; 84 | }; 85 | if (self.list.items.len > MAX_HISTORY_SIZE) 86 | self.allocator.free(self.list.orderedRemove(1)); 87 | } 88 | 89 | pub fn saveTemp(self: *History, line: []const u8) void { 90 | if (self.temp_buffer != null) 91 | self.clearTemp(); 92 | const line_copy = self.allocator.dupe(u8, line) catch return; 93 | self.temp_buffer = line_copy; 94 | } 95 | 96 | pub fn next(self: *History) ?[]const u8 { 97 | if (self.position < self.list.items.len) 98 | self.position += 1; 99 | if (self.list.items.len == self.position) 100 | return self.temp_buffer; 101 | return self.current(); 102 | } 103 | pub fn previous(self: *History) ?[]const u8 { 104 | if (self.position > 0) 105 | self.position -= 1; 106 | return self.current(); 107 | } 108 | 109 | pub fn current(self: *History) ?[]const u8 { 110 | if (self.position >= self.list.items.len and self.position < 0) 111 | return null 112 | else 113 | return self.list.items[self.position]; 114 | } 115 | 116 | pub fn getTemp(self: *History) ?[]const u8 { 117 | return self.temp_buffer; 118 | } 119 | pub fn clearTemp(self: *History) void { 120 | if (self.temp_buffer) |buf| 121 | self.allocator.free(buf); 122 | self.temp_buffer = null; 123 | } 124 | 125 | pub fn isLatest(self: *History) bool { 126 | if (self.list.items.len == 0) 127 | return true; 128 | return self.position >= self.list.items.len; 129 | } 130 | 131 | pub fn size(self: *History) usize { 132 | return self.list.items.len; 133 | } 134 | 135 | pub fn deinit(self: *History) void { 136 | if (!self.enabled) 137 | return; 138 | if (self.file) |path| { 139 | defer self.allocator.free(path); 140 | defer { 141 | for (self.list.items) |item| 142 | self.allocator.free(item); 143 | self.list.deinit(self.allocator); 144 | } 145 | 146 | const location = std.fs.path.dirname(path) orelse return; 147 | 148 | std.fs.cwd().makePath(location) catch |err| { 149 | std.debug.print("MkDirError: {}\n", .{err}); 150 | return; 151 | }; 152 | 153 | const history_file = std.fs.createFileAbsolute(path, .{}) catch return; 154 | defer history_file.close(); 155 | 156 | var buffer: [1024]u8 = undefined; 157 | var file_writer = history_file.writer(&buffer); 158 | const writer = &file_writer.interface; 159 | for (self.list.items) |data| { 160 | writer.writeAll(data) catch break; 161 | writer.writeByte('\n') catch break; 162 | } 163 | writer.flush() catch {}; 164 | } 165 | } 166 | --------------------------------------------------------------------------------