commit 7c2cde8a30d6b99150d64bbca507a857064ee67c Author: Jeffrey C. Ollie Date: Sun Sep 8 21:52:31 2024 -0500 first diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml new file mode 100644 index 0000000..82a2aa8 --- /dev/null +++ b/.forgejo/workflows/test.yaml @@ -0,0 +1,16 @@ +on: [push, pull_request] +name: Test +jobs: + test: + runs-on: nixos + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Update + run: nix flake update + - name: Test + run: nix develop .#default -c zig build test + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc6a357 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.zig-cache +/zig-out + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..623b89f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2004 Jeffrey C. Ollie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad23f80 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# circular buffer + +Simple circular buffer for Zig. Buffers must be sized in increments of powers of +two to simplify (and hopefully speed up) the math. diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..920956a --- /dev/null +++ b/build.zig @@ -0,0 +1,23 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("circular-buffer", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const mod_tests = b.addTest(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const run_mod_tests = b.addRunArtifact(mod_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_mod_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..d574cbe --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = "circular-buffer", + .version = "0.1.0", + + .minimum_zig_version = "0.13.0", + + .dependencies = .{}, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "LICENSE", + "README.md", + }, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ac13b9a --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1725634671, + "narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..086f4cb --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + inputs = { + nixpkgs = { + url = "nixpkgs/nixos-unstable"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = import nixpkgs { + inherit system; + }; + in { + devShells.default = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.zig_0_13 + ]; + shellHook = '' + export name=osc-fuzzer + ''; + }; + } + ); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..522c30d --- /dev/null +++ b/src/root.zig @@ -0,0 +1,207 @@ +const std = @import("std"); + +pub fn CircularBuffer(comptime T: type) type { + const info = @typeInfo(T); + std.debug.assert(info == .Int); + std.debug.assert(info.Int.signedness == .unsigned); + + return struct { + data: [std.math.maxInt(T) + 1]u8 = undefined, + head: T = 0, + tail: T = 0, + full: bool = false, + + pub fn isEmpty(self: *CircularBuffer(T)) bool { + return self.head == self.tail and !self.full; + } + + pub fn isFull(self: *CircularBuffer(T)) bool { + return self.full; + } + + pub fn reset(self: *CircularBuffer(T)) void { + self.head = 0; + self.tail = 0; + self.full = false; + } + + pub fn size(self: *CircularBuffer(size)) T { + if (self.full) return std.math.maxInt(T); + if (self.head >= self.tail) return @intCast(self.head - self.tail); + return @intCast(std.math.maxInt(T) + self.head - self.tail); + } + + fn advance(self: *CircularBuffer(T)) void { + self.head +%= 1; + if (self.full) self.tail +%= 1; + self.full = self.head == self.tail; + } + + fn retreat(self: *CircularBuffer(T)) void { + self.full = false; + self.tail -%= 1; + } + + pub fn pushByte(self: *CircularBuffer(T), byte: u8) void { + self.data[self.head] = byte; + self.advance(); + } + + pub fn popByte(self: *CircularBuffer(T)) !u8 { + if (self.isEmpty()) return error.BufferEmpty; + defer self.retreat(); + return self.data[self.tail]; + } + + pub fn pushAll(self: *CircularBuffer(T), data: []const u8) void { + if (data.len >= self.data.len) { + @memcpy(&self.data, data[(data.len - self.data.len)..]); + self.tail = 0; + self.head = 0; + self.full = true; + return; + } + + for (data) |c| self.pushByte(c); + } + + pub fn write(self: *CircularBuffer(T), writer: anytype) !void { + if (self.head <= self.tail) { + try writer.writeAll(self.data[self.tail..]); + try writer.writeAll(self.data[0..self.head]); + } else { + try writer.writeAll(self.data[self.tail..self.head]); + } + } + }; +} + +test "circular buffer 1" { + var cb = CircularBuffer(u2){}; + cb.pushByte('a'); + try std.testing.expect(!cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(1, cb.head); + try std.testing.expectEqual('a', cb.data[0]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("a", result); +} + +test "circular buffer 2" { + var cb = CircularBuffer(u2){}; + cb.pushByte('a'); + cb.pushByte('b'); + cb.pushByte('c'); + try std.testing.expect(!cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(3, cb.head); + try std.testing.expectEqual('a', cb.data[0]); + try std.testing.expectEqual('b', cb.data[1]); + try std.testing.expectEqual('c', cb.data[2]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("abc", result); +} + +test "circular buffer 3" { + var cb = CircularBuffer(u2){}; + cb.pushByte('a'); + cb.pushByte('b'); + cb.pushByte('c'); + cb.pushByte('d'); + cb.pushByte('e'); + try std.testing.expect(cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(1, cb.tail); + try std.testing.expectEqual(1, cb.head); + try std.testing.expectEqual('e', cb.data[0]); + try std.testing.expectEqual('b', cb.data[1]); + try std.testing.expectEqual('c', cb.data[2]); + try std.testing.expectEqual('d', cb.data[3]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("bcde", result); +} + +test "circular buffer 4" { + var cb = CircularBuffer(u2){}; + cb.pushAll("a"); + try std.testing.expect(!cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(1, cb.head); + try std.testing.expectEqual('a', cb.data[0]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("a", result); +} + +test "circular buffer 5" { + var cb = CircularBuffer(u2){}; + cb.pushAll("ab"); + try std.testing.expect(!cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(2, cb.head); + try std.testing.expectEqual('a', cb.data[0]); + try std.testing.expectEqual('b', cb.data[1]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("ab", result); +} + +test "circular buffer 6" { + var cb = CircularBuffer(u2){}; + cb.pushAll("abcd"); + try std.testing.expect(cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(0, cb.head); + try std.testing.expectEqual('a', cb.data[0]); + try std.testing.expectEqual('b', cb.data[1]); + try std.testing.expectEqual('c', cb.data[2]); + try std.testing.expectEqual('d', cb.data[3]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("abcd", result); +} + +test "circular buffer 7" { + var cb = CircularBuffer(u2){}; + cb.pushAll("abcde"); + try std.testing.expect(cb.isFull()); + try std.testing.expect(!cb.isEmpty()); + try std.testing.expectEqual(0, cb.tail); + try std.testing.expectEqual(0, cb.head); + try std.testing.expectEqual('b', cb.data[0]); + try std.testing.expectEqual('c', cb.data[1]); + try std.testing.expectEqual('d', cb.data[2]); + try std.testing.expectEqual('e', cb.data[3]); + + var tmp: [16]u8 = undefined; + var fbs = std.io.fixedBufferStream(&tmp); + try cb.write(fbs.writer()); + const result = fbs.getWritten(); + try std.testing.expectEqualStrings("bcde", result); +}