diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db3cb54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.zig-cache/ +/zig-out/ diff --git a/README.md b/README.md index e8451cc..21a64d0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,2 @@ -# Thespian -Fast & flexible actors for Zig, C & C++ applications - -To build: -``` -./zig build -``` - -See `tests/*` for many interesting examples. +# Zig CBOR +A Fast & flexible cbor encoding, decoding and matching library for Zig diff --git a/build.zig b/build.zig index 4c1fb66..aab561e 100644 --- a/build.zig +++ b/build.zig @@ -1,128 +1,21 @@ 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 95ed4e7..89f37b7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,19 +1,9 @@ .{ - .name = "thespian", - .version = "0.0.1", + .name = .cbor, + .version = "1.0.0", + .fingerprint = 0x1feaffc7fc04c445, - .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 f202518..2d791c8 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, .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| { + .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| { 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: []u8 = undefined; + var key: []const u8 = undefined; var value: json.Value = .null; if (!try matchString(&iter, &key)) @@ -522,6 +522,21 @@ 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) @@ -642,21 +657,22 @@ 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), - .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), + .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), }, - .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 => return matchFloatValue(T, iter, value), - .ComptimeFloat => matchFloatValue(f64, iter, value), + .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), else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), }; } @@ -784,7 +800,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, }; } @@ -837,15 +853,15 @@ fn Extractor(comptime T: type) type { pub fn extract(self: Self, iter: *[]const u8) Error!bool { switch (comptime @typeInfo(T)) { - .Int, .ComptimeInt => return matchInt(T, iter, self.dest), - .Bool => return matchBool(iter, self.dest), - .Pointer => |ptr_info| switch (ptr_info.size) { - .Slice => { + .int, .comptime_int => 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)) { @@ -854,7 +870,8 @@ fn Extractor(comptime T: type) type { } return false; }, - .Float => return matchFloat(T, iter, self.dest), + .float => return matchFloat(T, iter, self.dest), + .@"enum" => return matchEnum(T, iter, self.dest), else => extractError(T), } } @@ -863,8 +880,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)) { @@ -897,8 +914,11 @@ 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 { @@ -954,6 +974,12 @@ 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_cbor.zig b/test/tests.zig similarity index 99% rename from test/tests_cbor.zig rename to test/tests.zig index f3d52af..1d27410 100644 --- a/test/tests_cbor.zig +++ b/test/tests.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 = buf[0 .. iter.len + iter2.len]; + var iter3: []const u8 = buf[0 .. iter.len + iter2.len]; try expect(try matchValue(&iter3, 7)); try expect(try matchValue(&iter3, 8)); }