diff --git a/README.md b/README.md index 61a210e..e6d0b65 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# zig-template +# zig-smolstr -Template for zig repositories \ No newline at end of file +String with inlining support, based on [smol_str](https://github.com/rust-analyzer/smol_str) diff --git a/build.zig b/build.zig index 015d5a1..0976266 100644 --- a/build.zig +++ b/build.zig @@ -4,63 +4,34 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const lib_mod = b.createModule(.{ + const lib_mod = b.addModule("smolstr", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); - const exe_mod = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - exe_mod.addImport("zig_template_lib", lib_mod); - const lib = b.addLibrary(.{ .linkage = .static, - .name = "zig_template", + .name = "smolstr", .root_module = lib_mod, }); - const exe = b.addExecutable(.{ - .name = "zig_template", - .root_module = exe_mod, - }); - b.installArtifact(lib); - b.installArtifact(exe); - - const run_cmd = b.addRunArtifact(exe); - - run_cmd.step.dependOn(b.getInstallStep()); - - if (b.args) |args| { - run_cmd.addArgs(args); - } - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); const lib_unit_tests = b.addTest(.{ .root_module = lib_mod, }); - const exe_unit_tests = b.addTest(.{ - .root_module = exe_mod, - }); - const ext_tests = b.addTest(.{ .root_source_file = b.path("tests/root.zig"), }); + ext_tests.root_module.addImport("smolstr", lib.root_module); + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); const run_ext_tests = b.addRunArtifact(ext_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); - test_step.dependOn(&run_exe_unit_tests.step); test_step.dependOn(&run_ext_tests.step); } diff --git a/build.zig.zon b/build.zig.zon index ae2c268..f6d2a93 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,11 +6,11 @@ // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. - .name = .zig_template, + .name = .smolstr, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. - .version = "0.0.0", + .version = "0.0.1", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the @@ -24,7 +24,7 @@ // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. - .fingerprint = 0xf2a3560362615322, // Changing this has security and trust implications. + .fingerprint = 0x3dfbdb80e240f222, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. @@ -80,7 +80,7 @@ "build.zig.zon", "src", // For example... - //"LICENSE", + "LICENSE", //"README.md", }, } diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index f109629..0000000 --- a/src/main.zig +++ /dev/null @@ -1,11 +0,0 @@ -const std = @import("std"); - -pub fn main() !void { - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); - - try stdout.print("Hello, World.\n", .{}); - - try bw.flush(); -} diff --git a/src/root.zig b/src/root.zig index 95a0b68..e294c2c 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1 +1,95 @@ const std = @import("std"); + +const AllocError = std.mem.Allocator.Error; + +pub const SmolStr = struct { + pub const max_inline_len: usize = 22; + + const Inner = union(enum) { + inl: struct { + len: u8, + data: [22]u8, + }, + heap: []const u8, + }; + + inner: Inner, + + pub fn init(alloc: std.mem.Allocator, s: []const u8) AllocError!SmolStr { + if (s.len <= max_inline_len) { + return initInline(s); + } + + const s_ = try alloc.dupe(u8, s); + + const inner = Inner{ + .heap = s_, + }; + + return .{ .inner = inner }; + } + + pub fn initInline(s: []const u8) SmolStr { + if (s.len > max_inline_len) { + var buf: [1024]u8 = undefined; + + var msg: []const u8 = undefined; + + msg = std.fmt.bufPrint(&buf, "Tried to inline string \"{s}\" of len {}", .{ s, s.len }) catch |e| switch (e) { + error.NoSpaceLeft => blk: { + break :blk std.fmt.bufPrint(&buf, "Tried to inline string of len {}", .{s.len}) catch unreachable; + }, + }; + + @panic(msg); + } + + var inner = Inner{ .inl = .{ + .len = @intCast(s.len), + .data = undefined, + } }; + + @memcpy(inner.inl.data[0..s.len], s); + + return .{ .inner = inner }; + } + + pub fn deinit(self: SmolStr, alloc: std.mem.Allocator) void { + switch (self.inner) { + .inl => {}, + .heap => |s| alloc.free(s), + } + } + + pub fn str(self: *const SmolStr) []const u8 { + switch (self.inner) { + .inl => |*inl| return inl.data[0..self.len()], + .heap => |s| return s, + } + } + + pub fn len(self: SmolStr) usize { + switch (self.inner) { + .inl => |*inl| return inl.len, + .heap => |s| return s.len, + } + } + + pub fn dupe(self: SmolStr, alloc: std.mem.Allocator) AllocError!SmolStr { + switch (self.inner) { + .inl => return self, // dupe inlined string by direct copy + .heap => |s| return try SmolStr.init(alloc, s), + } + } + + pub fn format(self: SmolStr, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + + try writer.print("{s}", .{self.str()}); + } +}; + +test { + try std.testing.expectEqual(24, @sizeOf(SmolStr)); +} diff --git a/tests/root.zig b/tests/root.zig index 95a0b68..b044c4d 100644 --- a/tests/root.zig +++ b/tests/root.zig @@ -1 +1,70 @@ const std = @import("std"); + +const SmolStr = @import("smolstr").SmolStr; + +test "inline" { + const alloc = std.testing.allocator; + + const s = "abcdefghijklmnopqrstuvwxyz"; + + for (1..SmolStr.max_inline_len + 1) |i| { + const sub_s: []const u8 = s[0..i]; + + const str = try SmolStr.init(alloc, sub_s); + defer str.deinit(alloc); + + try std.testing.expectEqual(i, str.inner.inl.len); + try std.testing.expectEqualStrings(s[0..i], str.inner.inl.data[0..str.inner.inl.len]); + + try std.testing.expectEqual(i, str.len()); + try std.testing.expectEqualStrings(s[0..i], str.str()); + + try std.testing.expectEqual(s[0], str.str()[0]); + } +} + +test "heap" { + const alloc = std.testing.allocator; + + const s = "abcdefghijklmnopqrstuvwxyz"; + + const str = try SmolStr.init(alloc, s); + defer str.deinit(alloc); + + try std.testing.expectEqualStrings(s, str.inner.heap); + + try std.testing.expectEqual(s.len, str.len()); + try std.testing.expectEqualStrings(s, str.str()); +} + +test "dupe inline" { + const alloc = std.testing.allocator; + + { + const s = "abcd"; + + const str1 = try SmolStr.init(alloc, s); + defer str1.deinit(alloc); + + const str2 = try str1.dupe(alloc); + defer str2.deinit(alloc); + + try std.testing.expectEqualSlices(u8, std.mem.asBytes(&str1), std.mem.asBytes(&str2)); + } + + { + const s = "abcdefghijklmnopqrstuvwxyz"; + + const str1 = try SmolStr.init(alloc, s); + defer str1.deinit(alloc); + + const str2 = try str1.dupe(alloc); + defer str2.deinit(alloc); + + // try std.testing.expectEqualSlices(u8, std.mem.asBytes(&str1), std.mem.asBytes(&str2)); + + try std.testing.expectEqualStrings(str1.str(), str2.str()); + + try std.testing.expect(str1.inner.heap.ptr != str2.inner.heap.ptr); + } +}