diff --git a/.gitignore b/.gitignore deleted file mode 100644 index db3cb54..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/.zig-cache/ -/zig-out/ diff --git a/README.md b/README.md index 21a64d0..e8451cc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# Zig CBOR -A Fast & flexible cbor encoding, decoding and matching library for Zig +# Thespian +Fast & flexible actors for Zig, C & C++ applications + +To build: +``` +./zig build +``` + +See `tests/*` for many interesting examples. diff --git a/build.zig b/build.zig index aab561e..4c1fb66 100644 --- a/build.zig +++ b/build.zig @@ -1,21 +1,128 @@ const std = @import("std"); +const CrossTarget = std.zig.CrossTarget; + +const cppflags = [_][]const u8{ + "-DASIO_HAS_THREADS", + "-fcolor-diagnostics", + "-std=c++20", + "-Wall", + "-Wextra", + "-Werror", + "-Wpedantic", + "-Wno-deprecated-declarations", + "-Wno-unqualified-std-cast-call", + "-Wno-bitwise-instead-of-logical", //for notcurses + "-fno-sanitize=undefined", + "-gen-cdb-fragment-path", + ".cache/cdb", +}; + pub fn build(b: *std.Build) void { + const enable_tracy_option = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)"); + const tracy_enabled = if (enable_tracy_option) |enabled| enabled else false; + + const options = b.addOptions(); + options.addOption(bool, "enable_tracy", tracy_enabled); + + const options_mod = options.createModule(); + const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const mode = .{ .target = target, .optimize = optimize }; + + const asio_dep = b.dependency("asio", mode); + const tracy_dep = if (tracy_enabled) b.dependency("tracy", mode) else undefined; + + const lib = b.addStaticLibrary(.{ + .name = "thespian", + .target = target, + .optimize = optimize, + }); + if (tracy_enabled) { + lib.defineCMacro("TRACY_ENABLE", null); + lib.defineCMacro("TRACY_CALLSTACK", null); + } + lib.addIncludePath(b.path("src")); + lib.addIncludePath(b.path("include")); + lib.addCSourceFiles(.{ .files = &[_][]const u8{ + "src/backtrace.cpp", + "src/c/context.cpp", + "src/c/env.cpp", + "src/c/file_descriptor.cpp", + "src/c/file_stream.cpp", + "src/c/handle.cpp", + "src/c/instance.cpp", + "src/c/metronome.cpp", + "src/c/signal.cpp", + "src/c/timeout.cpp", + "src/c/trace.cpp", + "src/cbor.cpp", + "src/executor_asio.cpp", + "src/hub.cpp", + "src/instance.cpp", + "src/trace.cpp", + }, .flags = &cppflags }); + if (tracy_enabled) { + lib.linkLibrary(tracy_dep.artifact("tracy")); + } + lib.linkLibrary(asio_dep.artifact("asio")); + if (lib.rootModuleTarget().os.tag == .windows) { + lib.linkSystemLibrary("mswsock"); + lib.linkSystemLibrary("ws2_32"); + } + lib.linkLibCpp(); + b.installArtifact(lib); const cbor_mod = b.addModule("cbor", .{ .root_source_file = b.path("src/cbor.zig"), }); + const thespian_mod = b.addModule("thespian", .{ + .root_source_file = b.path("src/thespian.zig"), + .imports = &.{ + .{ .name = "cbor", .module = cbor_mod }, + }, + }); + thespian_mod.addIncludePath(b.path("include")); + thespian_mod.linkLibrary(lib); + const tests = b.addTest(.{ .root_source_file = b.path("test/tests.zig"), .target = target, .optimize = optimize, }); + + tests.root_module.addImport("build_options", options_mod); tests.root_module.addImport("cbor", cbor_mod); + tests.root_module.addImport("thespian", thespian_mod); + tests.addIncludePath(b.path("test")); + tests.addIncludePath(b.path("src")); + tests.addIncludePath(b.path("include")); + tests.addCSourceFiles(.{ .files = &[_][]const u8{ + "test/cbor_match.cpp", + "test/debug.cpp", + "test/endpoint_unx.cpp", + "test/endpoint_tcp.cpp", + "test/hub_filter.cpp", + "test/ip_tcp_client_server.cpp", + "test/ip_udp_echo.cpp", + "test/metronome_test.cpp", + "test/perf_cbor.cpp", + "test/perf_hub.cpp", + "test/perf_ring.cpp", + "test/perf_spawn.cpp", + "test/spawn_exit.cpp", + "test/tests.cpp", + "test/timeout_test.cpp", + }, .flags = &cppflags }); + tests.linkLibrary(lib); + tests.linkLibrary(asio_dep.artifact("asio")); + tests.linkLibCpp(); + b.installArtifact(tests); const test_run_cmd = b.addRunArtifact(tests); + const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&test_run_cmd.step); } diff --git a/build.zig.zon b/build.zig.zon index 89f37b7..95ed4e7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,9 +1,19 @@ .{ - .name = .cbor, - .version = "1.0.0", - .fingerprint = 0x1feaffc7fc04c445, + .name = "thespian", + .version = "0.0.1", + .dependencies = .{ + .asio = .{ + .url = "https://github.com/neurocyte/asio/archive/8cc76c9ed4054f45bfc06476d795477096aab7a5.tar.gz", + .hash = "12206a4050ebb2e2bf84ed477ea5fe0d0325f9292eef2cb8bd47ccda75a9b3d93516", + }, + .tracy = .{ + .url = "https://github.com/neurocyte/zig-tracy/archive/80b914d2391209de9ed5a1fd6f440642df55cbd4.tar.gz", + .hash = "1220351c8410936854e3baa10aa7bbe775196b3974a3d670808bebbab00631439285", + }, + }, .paths = .{ + "include", "src", "test", "build.zig", diff --git a/src/cbor.zig b/src/cbor.zig index 2d791c8..f202518 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -209,12 +209,12 @@ fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { const T = @TypeOf(value); switch (@typeInfo(T)) { - .int, .comptime_int => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)), - .bool => return writeBool(writer, value), - .optional => return if (value) |v| writeValue(writer, v) else writeNull(writer), - .error_union => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), - .error_set => return writeErrorset(writer, value), - .@"union" => |info| { + .Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)), + .Bool => return writeBool(writer, value), + .Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer), + .ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), + .ErrorSet => return writeErrorset(writer, value), + .Union => |info| { if (info.tag_type) |TagType| { comptime var v = void; inline for (info.fields) |u_field| { @@ -230,7 +230,7 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { try writeArray(writer, .{@typeName(T)}); } }, - .@"struct" => |info| { + .Struct => |info| { if (info.is_tuple) { if (info.fields.len == 0) return writeNull(writer); try writeArrayHeader(writer, info.fields.len); @@ -245,10 +245,10 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { } } }, - .pointer => |ptr_info| switch (ptr_info.size) { - .one => return writeValue(writer, value.*), - .many, .c => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), - .slice => { + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => return writeValue(writer, value.*), + .Many, .C => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), + .Slice => { if (ptr_info.child == u8) return writeString(writer, value); if (value.len == 0) return writeNull(writer); try writeArrayHeader(writer, value.len); @@ -256,22 +256,22 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { try writeValue(writer, elem); }, }, - .array => |info| { + .Array => |info| { if (info.child == u8) return writeString(writer, &value); if (value.len == 0) return writeNull(writer); try writeArrayHeader(writer, value.len); for (value) |elem| try writeValue(writer, elem); }, - .vector => |info| { + .Vector => |info| { try writeArrayHeader(writer, info.len); var i: usize = 0; while (i < info.len) : (i += 1) { try writeValue(writer, value[i]); } }, - .null => try writeNull(writer), - .float => |info| switch (info.bits) { + .Null => try writeNull(writer), + .Float => |info| switch (info.bits) { 16 => try writeF16(writer, value), 32 => try writeF32(writer, value), 64 => try writeF64(writer, value), @@ -380,7 +380,7 @@ fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) Error!b var iter = iter_.*; var n = try decodePInt(&iter, minor); while (n > 0) { - var key: []const u8 = undefined; + var key: []u8 = undefined; var value: json.Value = .null; if (!try matchString(&iter, &key)) @@ -522,21 +522,6 @@ fn matchFloatValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { return if (try matchFloat(T, iter, &v)) v == val else false; } -pub fn matchEnum(comptime T: type, iter_: *[]const u8, val: *T) Error!bool { - var iter = iter_.*; - var str: []const u8 = undefined; - if (try matchString(&iter, &str)) if (std.meta.stringToEnum(T, str)) |val_| { - val.* = val_; - iter_.* = iter; - return true; - }; - return false; -} - -fn matchEnumValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { - return matchStringValue(iter, @tagName(val)); -} - fn skipString(iter: *[]const u8, minor: u5) Error!void { const len: usize = @intCast(try decodePInt(iter, minor)); if (iter.len < len) @@ -657,22 +642,21 @@ pub fn matchValue(iter: *[]const u8, value: anytype) Error!bool { if (comptime isExtractor(T)) return value.extract(iter); return switch (comptime @typeInfo(T)) { - .int => return matchIntValue(T, iter, value), - .comptime_int => return matchIntValue(i64, iter, value), - .bool => matchBoolValue(iter, value), - .pointer => |info| switch (info.size) { - .one => matchValue(iter, value.*), - .many, .c => matchError(T), - .slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info), + .Int => return matchIntValue(T, iter, value), + .ComptimeInt => return matchIntValue(i64, iter, value), + .Bool => matchBoolValue(iter, value), + .Pointer => |info| switch (info.size) { + .One => matchValue(iter, value.*), + .Many, .C => matchError(T), + .Slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info), }, - .@"struct" => |info| if (info.is_tuple) + .Struct => |info| if (info.is_tuple) matchArray(iter, value, info) else matchError(T), - .array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info), - .float => matchFloatValue(T, iter, value), - .comptime_float => matchFloatValue(f64, iter, value), - .@"enum" => matchEnumValue(T, iter, value), + .Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info), + .Float => return matchFloatValue(T, iter, value), + .ComptimeFloat => matchFloatValue(f64, iter, value), else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), }; } @@ -800,7 +784,7 @@ fn hasExtractorTag(info: anytype) bool { fn isExtractor(comptime T: type) bool { return comptime switch (@typeInfo(T)) { - .@"struct" => |info| hasExtractorTag(info), + .Struct => |info| hasExtractorTag(info), else => false, }; } @@ -853,15 +837,15 @@ fn Extractor(comptime T: type) type { pub fn extract(self: Self, iter: *[]const u8) Error!bool { switch (comptime @typeInfo(T)) { - .int, .comptime_int => return matchInt(T, iter, self.dest), - .bool => return matchBool(iter, self.dest), - .pointer => |ptr_info| switch (ptr_info.size) { - .slice => { + .Int, .ComptimeInt => return matchInt(T, iter, self.dest), + .Bool => return matchBool(iter, self.dest), + .Pointer => |ptr_info| switch (ptr_info.size) { + .Slice => { if (ptr_info.child == u8) return matchString(iter, self.dest) else extractError(T); }, else => extractError(T), }, - .optional => |opt_info| { + .Optional => |opt_info| { var nested: opt_info.child = undefined; const extractor = Extractor(opt_info.child).init(&nested); if (try extractor.extract(iter)) { @@ -870,8 +854,7 @@ fn Extractor(comptime T: type) type { } return false; }, - .float => return matchFloat(T, iter, self.dest), - .@"enum" => return matchEnum(T, iter, self.dest), + .Float => return matchFloat(T, iter, self.dest), else => extractError(T), } } @@ -880,8 +863,8 @@ fn Extractor(comptime T: type) type { fn ExtractorType(comptime T: type) type { const T_type_info = @typeInfo(T); - if (T_type_info != .pointer) @compileError("extract requires a pointer argument"); - return Extractor(T_type_info.pointer.child); + if (T_type_info != .Pointer) @compileError("extract requires a pointer argument"); + return Extractor(T_type_info.Pointer.child); } pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { @@ -914,11 +897,8 @@ pub fn extract_cbor(dest: *[]const u8) CborExtractor { } pub fn JsonStream(comptime T: type) type { - return JsonStreamWriter(T.Writer); -} - -pub fn JsonStreamWriter(comptime Writer: type) type { return struct { + const Writer = T.Writer; const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 }); fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { @@ -974,12 +954,6 @@ pub fn toJson(cbor_buf: []const u8, json_buf: []u8) (JsonEncodeError || error{No return fbs.getWritten(); } -pub fn toJsonWriter(cbor_buf: []const u8, writer: anytype, options: std.json.StringifyOptions) !void { - var s = json.writeStream(writer, options); - var iter: []const u8 = cbor_buf; - try JsonStreamWriter(@TypeOf(writer)).jsonWriteValue(&s, &iter); -} - pub fn toJsonAlloc(a: std.mem.Allocator, cbor_buf: []const u8) (JsonEncodeError)![]const u8 { var buf = std.ArrayList(u8).init(a); defer buf.deinit(); diff --git a/test/tests.zig b/test/tests_cbor.zig similarity index 99% rename from test/tests.zig rename to test/tests_cbor.zig index 1d27410..f3d52af 100644 --- a/test/tests.zig +++ b/test/tests_cbor.zig @@ -151,7 +151,7 @@ test "cbor.matchValue(i64) multi" { var buf: [128]u8 = undefined; const iter = fmt(&buf, 7); const iter2 = fmt(buf[iter.len..], 8); - var iter3: []const u8 = buf[0 .. iter.len + iter2.len]; + var iter3 = buf[0 .. iter.len + iter2.len]; try expect(try matchValue(&iter3, 7)); try expect(try matchValue(&iter3, 8)); }