From 77ef20eff53f6f3e40a2f7ee87f38ab4402cac4f Mon Sep 17 00:00:00 2001 From: "Nurul Huda (Apon)" Date: Mon, 26 Jan 2026 11:34:26 +0600 Subject: [PATCH] refactor: use temporal_rs as pkg --- .gitignore | 10 +- Cargo.lock | 477 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 21 ++ build.zig | 258 ++++++++++------------- build.zig.zon | 16 +- example/build.zig | 27 +++ example/build.zig.zon | 16 ++ example/src/main.zig | 16 ++ src/Duration.zig | 418 ++++++++++-------------------------- src/Instant.zig | 443 ++++++++++++--------------------------- src/cabi.zig | 263 +++++++++++++++++++++++ src/lib.rs | 1 + src/main.zig | 2 - src/root.zig | 1 + src/unwind_stubs.zig | 29 +++ 15 files changed, 1227 insertions(+), 771 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 example/build.zig create mode 100644 example/build.zig.zon create mode 100644 example/src/main.zig create mode 100644 src/cabi.zig create mode 100644 src/lib.rs create mode 100644 src/unwind_stubs.zig diff --git a/.gitignore b/.gitignore index dab555a..363a6ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ zig-out .zig-cache +target -# Ignore Rust build target directory -vendor/temporal/target - -# But track the specific compiled libraries -!vendor/temporal/target/**/libtemporal.a -!vendor/temporal/target/**/temporal.lib +lib +!lib/*/libtemporal_capi.a +!lib/*/temporal_capi.lib tmp \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..84df04e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,477 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "calendrical_calculations" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7" +dependencies = [ + "core_maths", + "displaydoc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "diplomat" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adb46b05e2f53dcf6a7dfc242e4ce9eb60c369b6b6eb10826a01e93167f59c6" +dependencies = [ + "diplomat_core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diplomat-runtime" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0569bd3caaf13829da7ee4e83dbf9197a0e1ecd72772da6d08f0b4c9285c8d29" + +[[package]] +name = "diplomat_core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51731530ed7f2d4495019abc7df3744f53338e69e2863a6a64ae91821c763df1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "smallvec", + "strck", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "icu_calendar" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locale", + "icu_locale_core", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_calendar_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locale_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ixdtf" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992" + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "serde_core", + "writeable", + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strck" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "temporal_capi" +version = "0.1.2" +dependencies = [ + "temporal_capi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "temporal_rs", +] + +[[package]] +name = "temporal_capi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a151e402c2bdb6a3a2a2f3f225eddaead2e7ce7dd5d3fa2090deb11b17aa4ed8" +dependencies = [ + "diplomat", + "diplomat-runtime", + "icu_calendar", + "icu_locale", + "num-traits", + "temporal_rs", + "timezone_provider", + "writeable", +] + +[[package]] +name = "temporal_rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88afde3bd75d2fc68d77a914bece426aa08aa7649ffd0cdd4a11c3d4d33474d1" +dependencies = [ + "core_maths", + "icu_calendar", + "icu_locale", + "ixdtf", + "num-traits", + "timezone_provider", + "tinystr", + "writeable", +] + +[[package]] +name = "timezone_provider" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9ba0000e9e73862f3e7ca1ff159e2ddf915c9d8bb11e38a7874760f445d993" +dependencies = [ + "combine", + "jiff-tzdb", + "tinystr", + "tzif", + "zerotrie", + "zerovec", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tzif" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0376dfa52cce372f3b095010fd064fb850a5d8fbfd5be8b0ffa3d64eeab5a5d" +dependencies = [ + "combine", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0e2f259 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "temporal_capi" +version = "0.1.2" +edition = "2024" + +[dependencies.temporal_capi] +version = "0.1.2" +default-features = false +features = ["compiled_data"] + +[dependencies.temporal_rs] +version = "*" +default-features = false +features = ["float64_representable_durations"] + +[lib] +crate-type = ["staticlib"] + +[profile.release] +strip = true +lto = true diff --git a/build.zig b/build.zig index 5365141..9c941f6 100644 --- a/build.zig +++ b/build.zig @@ -1,22 +1,85 @@ const std = @import("std"); +const build_crab = @import("build_crab"); -pub fn build(b: *std.Build) void { +pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + // --- Zig Module: temporalz --- // const mod = b.addModule("temporalz", .{ .root_source_file = b.path("src/root.zig"), .target = target, + .optimize = optimize, }); - mod.addObjectFile(b.path(getTemporalRsPath(target))); - mod.link_libc = true; - if (target.result.os.tag == .linux) mod.linkSystemLibrary("unwind", .{}); - if (target.result.os.tag == .windows) { - mod.linkSystemLibrary("ws2_32", .{}); - mod.linkSystemLibrary("bcrypt", .{}); - mod.linkSystemLibrary("advapi32", .{}); - mod.linkSystemLibrary("userenv", .{}); + + // --- Rust C ABI: temporal_capi --- // + const temporal_rs = b.dependency("temporal_rs", .{ .target = target, .optimize = optimize }); + mod.addIncludePath(temporal_rs.path("temporal_capi/bindings/c")); + + // --- Rust Crate: temporal_rs --- // + { + // Determine target triple string for pre-built library lookup + const arch_str = @tagName(target.result.cpu.arch); + const os_str = @tagName(target.result.os.tag); + const target_triple = b.fmt("{s}-{s}", .{ arch_str, os_str }); + const lib_name = if (target.result.os.tag == .windows) "temporal_capi.lib" else "libtemporal_capi.a"; + + // Check if pre-built library exists in lib// + const prebuilt_lib_path = b.fmt("lib/{s}/{s}", .{ target_triple, lib_name }); + const prebuilt_lib_file = b.path(prebuilt_lib_path); + + // Try to use pre-built library if it exists by checking the path object + const use_prebuilt = blk: { + // Use the LazyPath to check if the library exists + const lib_full_path = prebuilt_lib_file.getPath(b); + const lib_check_file = std.fs.openFileAbsolute(lib_full_path, .{}) catch break :blk false; + lib_check_file.close(); + break :blk true; + }; + + if (use_prebuilt) { + // Use pre-built library (no Rust compiler needed) + // std.debug.print("Using pre-built temporal_capi library: {s}\n", .{prebuilt_lib_path}); + mod.addObjectFile(prebuilt_lib_file); + } else { + // Build from source using Cargo + const build_dir = build_crab.addCargoBuild( + b, + .{ + .manifest_path = b.path("Cargo.toml"), + .cargo_args = if (optimize == .Debug) &.{} else &.{"--release"}, + }, + .{ + .target = target, + .optimize = .ReleaseSafe, + }, + ); + + // Install .a/.lib to lib//libtemporal_capi.a/temporal_capi.lib + const install_lib = b.addInstallDirectory(.{ + .source_dir = build_dir, + .install_dir = .{ .custom = "../lib" }, + .install_subdir = target_triple, + }); + b.getInstallStep().dependOn(&install_lib.step); + mod.addObjectFile(build_dir.path(b, lib_name)); + } + + // --- Rust Misc Deps --- // + if (target.result.os.tag == .windows) mod.linkSystemLibrary("userenv", .{}); + const unwind_stubs = b.addLibrary(.{ + .linkage = .static, + .name = "unwind_stubs", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/unwind_stubs.zig"), + .target = target, + .optimize = optimize, + }), + }); + mod.linkLibrary(unwind_stubs); } + + // --- Zig Executable: temporalz --- // const exe = b.addExecutable(.{ .name = "temporalz", .root_module = b.createModule(.{ @@ -30,20 +93,21 @@ pub fn build(b: *std.Build) void { }); b.installArtifact(exe); - // Run command - 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"); + // --- Steps: Run --- // + { + 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); + } - // Docs + // --- Steps: Docs --- // { const docs_step = b.step("docs", "Build the temporalz docs"); - const docs_obj = b.addObject(.{ - .name = "temporalz", - .root_module = mod, - }); + const docs_obj = b.addObject(.{ .name = "temporalz", .root_module = mod }); const docs = docs_obj.getEmittedDocs(); + docs_step.dependOn(&b.addInstallDirectory(.{ .source_dir = docs, .install_dir = .prefix, @@ -51,9 +115,8 @@ pub fn build(b: *std.Build) void { }).step); } - // Tests + // --- Steps: Test --- // { - run_step.dependOn(&run_cmd.step); const test_step = b.step("test", "Run tests"); const mod_tests = b.addTest(.{ .root_module = mod, @@ -63,141 +126,46 @@ pub fn build(b: *std.Build) void { }, }); mod_tests.linkLibC(); - if (target.result.os.tag == .linux) mod_tests.linkSystemLibrary("unwind"); - if (target.result.os.tag == .windows) { - mod_tests.linkSystemLibrary("ws2_32"); - mod_tests.linkSystemLibrary("bcrypt"); - mod_tests.linkSystemLibrary("advapi32"); - } test_step.dependOn(&b.addRunArtifact(mod_tests).step); const exe_tests = b.addTest(.{ .root_module = exe.root_module }); test_step.dependOn(&b.addRunArtifact(exe_tests).step); } - // Rust cross-compilation prebuild + // --- Steps: Build all platforms --- // { - const rust_prebuild_step = b.step("temporal-rs", "Build and vendor Rust staticlibs for supported targets"); - var rustup_args: [3 + rust_targets.len][]const u8 = undefined; - rustup_args[0] = "rustup"; - rustup_args[1] = "target"; - rustup_args[2] = "add"; - for (rust_targets, 0..) |t, i| { - rustup_args[3 + i] = t.triple; - } - const rustup_add = b.addSystemCommand(&rustup_args); - rust_prebuild_step.dependOn(&rustup_add.step); + const build_lib_step = b.step("lib", "Build libraries for all common platforms"); - for (rust_targets) |t| { - const cargo_build = b.addSystemCommand(&.{ - "cargo", - "build", - "--release", - "--manifest-path", - "vendor/temporal/Cargo.toml", - "--target", - t.triple, - }); - cargo_build.step.dependOn(&rustup_add.step); - rust_prebuild_step.dependOn(&cargo_build.step); - } - } + inline for (platforms) |p| { + const query = try std.Build.parseTargetQuery(.{ .arch_os_abi = p }); + const platform_target = b.resolveTargetQuery(query); - // Release builds for all platforms - { - const release_targets = [_]struct { - name: []const u8, - target: std.Target.Query, - }{ - .{ .name = "linux-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .linux } }, - .{ .name = "linux-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .linux } }, - .{ .name = "macos-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .macos } }, - .{ .name = "macos-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .macos } }, - .{ .name = "windows-x64", .target = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .msvc } }, - .{ .name = "windows-aarch64", .target = .{ .cpu_arch = .aarch64, .os_tag = .windows, .abi = .msvc } }, - }; - - const release_step = b.step("release", "Build release binaries for all targets"); - - for (release_targets) |release_target| { - const resolved_target = b.resolveTargetQuery(release_target.target); - - const release_mod = b.addModule(b.fmt("temporalz-{s}", .{release_target.name}), .{ - .root_source_file = b.path("src/root.zig"), - .target = resolved_target, - }); - release_mod.addObjectFile(b.path(getTemporalRsPath(resolved_target))); - release_mod.link_libc = true; - if (resolved_target.result.os.tag == .linux) release_mod.linkSystemLibrary("unwind", .{}); - if (resolved_target.result.os.tag == .windows) { - release_mod.linkSystemLibrary("ws2_32", .{}); - release_mod.linkSystemLibrary("bcrypt", .{}); - release_mod.linkSystemLibrary("advapi32", .{}); - release_mod.linkSystemLibrary("userenv", .{}); - } - - const release_exe = b.addExecutable(.{ - .name = "temporalz", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = resolved_target, - .optimize = .ReleaseSmall, - .imports = &.{ - .{ .name = "temporalz", .module = release_mod }, - }, - }), - }); - release_exe.linkLibC(); - if (resolved_target.result.os.tag == .windows) { - release_exe.linkSystemLibrary("ws2_32"); - release_exe.linkSystemLibrary("bcrypt"); - release_exe.linkSystemLibrary("advapi32"); - release_exe.linkSystemLibrary("userenv"); - } - - const exe_ext = if (resolved_target.result.os.tag == .windows) ".exe" else ""; - const install_release = b.addInstallArtifact(release_exe, .{ - .dest_sub_path = b.fmt("release/temporalz-{s}{s}", .{ release_target.name, exe_ext }), - }); - - const target_step = b.step( - b.fmt("release-{s}", .{release_target.name}), - b.fmt("Build release binary for {s}", .{release_target.name}), + const build_dir = build_crab.addCargoBuild( + b, + .{ + .manifest_path = b.path("Cargo.toml"), + .cargo_args = &.{"--release"}, + }, + .{ + .target = platform_target, + .optimize = .ReleaseSafe, + }, ); - target_step.dependOn(&install_release.step); - release_step.dependOn(&install_release.step); + + const install_lib = b.addInstallDirectory(.{ + .source_dir = build_dir, + .install_dir = .{ .custom = "../lib" }, + .install_subdir = p, + }); + build_lib_step.dependOn(&install_lib.step); } } } -// Supported cross-compilation targets -const rust_targets = [_]struct { triple: []const u8 }{ - .{ .triple = "aarch64-apple-darwin" }, - .{ .triple = "x86_64-apple-darwin" }, - .{ .triple = "x86_64-unknown-linux-gnu" }, - .{ .triple = "aarch64-unknown-linux-gnu" }, - .{ .triple = "x86_64-pc-windows-msvc" }, - .{ .triple = "aarch64-pc-windows-msvc" }, +const platforms = [_][]const u8{ + "aarch64-macos", + "x86_64-macos", + "aarch64-linux-gnu", + "x86_64-linux-gnu", + "x86_64-windows-gnu", + "aarch64-windows-gnu", }; - -// Platform-specific Rust library path resolution -fn getTemporalRsPath(target: std.Build.ResolvedTarget) []const u8 { - const arch_tag = target.result.cpu.arch; - return switch (target.result.os.tag) { - .macos => switch (arch_tag) { - .aarch64 => "vendor/temporal/target/aarch64-apple-darwin/release/libtemporal.a", - .x86_64 => "vendor/temporal/target/x86_64-apple-darwin/release/libtemporal.a", - else => @panic("unsupported macOS architecture"), - }, - .linux => switch (arch_tag) { - .x86_64 => "vendor/temporal/target/x86_64-unknown-linux-gnu/release/libtemporal.a", - .aarch64 => "vendor/temporal/target/aarch64-unknown-linux-gnu/release/libtemporal.a", - else => @panic("unsupported Linux architecture"), - }, - .windows => switch (arch_tag) { - .x86_64 => "vendor/temporal/target/x86_64-pc-windows-msvc/release/temporal.lib", - .aarch64 => "vendor/temporal/target/aarch64-pc-windows-msvc/release/temporal.lib", - else => @panic("unsupported Windows architecture"), - }, - else => @panic("unsupported OS"), - }; -} diff --git a/build.zig.zon b/build.zig.zon index ff9d3fd..210f650 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,11 +3,23 @@ .version = "0.0.0", .fingerprint = 0xd8d79d59acc4faae, .minimum_zig_version = "0.15.2", - .dependencies = .{}, + .dependencies = .{ + // .temporal_rs = .{ + // .path = "./pkg/temporal_rs", + // }, + .temporal_rs = .{ + .url = "git+https://github.com/boa-dev/temporal#v0.1.2", + .hash = "N-V-__8AANlkNAB77Z946lqp-lgp2qSsOBDADValZC86PhB-", + }, + .build_crab = .{ + .url = "git+https://github.com/linusg/build.crab#7c8ace3739023b88f1fb95151df4f5b65bd7a311", + .hash = "build_crab-0.2.1-U0id_33BAAD3l81W7CBvgLU3OJSw7WI6FVLBSEHTnjTX", + }, + }, .paths = .{ "build.zig", "build.zig.zon", "src", - "vendor", + "lib", }, } diff --git a/example/build.zig b/example/build.zig new file mode 100644 index 0000000..d4cfa15 --- /dev/null +++ b/example/build.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const temporalz_dep = b.dependency("temporalz", .{ .target = target, .optimize = optimize }); + const exe = b.addExecutable(.{ + .name = "example", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "temporalz", .module = temporalz_dep.module("temporalz") }, + }, + }), + }); + b.installArtifact(exe); + + const run_step = b.step("run", "Run the app"); + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); +} diff --git a/example/build.zig.zon b/example/build.zig.zon new file mode 100644 index 0000000..6a75e1c --- /dev/null +++ b/example/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = .temporalz_example, + .version = "0.1.2", + .fingerprint = 0x686c9dfc777f9593, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .temporalz = .{ + .path = "../", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/src/main.zig b/example/src/main.zig new file mode 100644 index 0000000..7ecb9a2 --- /dev/null +++ b/example/src/main.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const Temporal = @import("temporalz"); + +pub fn main() !void { + const allocator = std.heap.page_allocator; + const result = try Temporal.Instant.init(1_704_067_200_000_000_000); // 2024-01-01 00:00:00 UTC + defer result.deinit(); + + std.debug.print("Instant.epoch_milliseconds: {}\n", .{result.epoch_milliseconds}); + std.debug.print("Instant.epoch_nanoseconds: {}\n", .{result.epoch_nanoseconds}); + const instant_str = try result.toString(allocator, .{}); + std.debug.print("Instant.toString(): {s}\n", .{instant_str}); + + const dur = try Temporal.Duration.from("P1Y2M3DT4H5M6S"); + defer dur.deinit(); +} diff --git a/src/Duration.zig b/src/Duration.zig index 602b35f..502aabc 100644 --- a/src/Duration.zig +++ b/src/Duration.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const temporal_rs = @import("cabi.zig"); +const c = temporal_rs.c; /// The Zig implementation of `Temporal.Duration`. /// @@ -135,7 +137,7 @@ const std = @import("std"); /// [mdn-duration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration const Duration = @This(); -_inner: *CDuration, +_inner: *c.Duration, /// Construct a Duration from years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds. /// Equivalent to `Temporal.Duration.from()` or the constructor. @@ -151,7 +153,7 @@ pub fn init( microseconds_val: f64, nanoseconds_val: f64, ) !Duration { - return wrapDuration(temporal_rs_Duration_try_new( + return wrapDuration(c.temporal_rs_Duration_try_new( years_val, months_val, weeks_val, @@ -167,156 +169,156 @@ pub fn init( /// Parse an ISO 8601 duration string (Temporal.Duration.from). pub fn from(text: []const u8) !Duration { - const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; - return wrapDuration(temporal_rs_Duration_from_utf8(view)); + const view = temporal_rs.toDiplomatStringView(text); + return wrapDuration(c.temporal_rs_Duration_from_utf8(view)); } /// Parse an ISO 8601 UTF-16 duration string. fn fromUtf16(text: []const u16) !Duration { - const view = DiplomatString16View{ .data = text.ptr, .len = text.len }; - return wrapDuration(temporal_rs_Duration_from_utf16(view)); + const view = temporal_rs.toDiplomatString16View(text); + return wrapDuration(c.temporal_rs_Duration_from_utf16(view)); } /// Create a Duration from a partial duration (where some fields may be omitted). fn fromPartialDuration(partial: PartialDuration) !Duration { - return wrapDuration(temporal_rs_Duration_from_partial_duration(partial)); + return wrapDuration(c.temporal_rs_Duration_from_partial_duration(partial)); } /// Check if the time portion of the duration is within valid ranges. fn isTimeWithinRange(self: Duration) bool { - return temporal_rs_Duration_is_time_within_range(self._inner); + return c.temporal_rs_Duration_is_time_within_range(self._inner); } /// Get the years component of the duration. pub fn years(self: Duration) i64 { - return temporal_rs_Duration_years(self._inner); + return c.temporal_rs_Duration_years(self._inner); } /// Get the months component of the duration. pub fn months(self: Duration) i64 { - return temporal_rs_Duration_months(self._inner); + return c.temporal_rs_Duration_months(self._inner); } /// Get the weeks component of the duration. pub fn weeks(self: Duration) i64 { - return temporal_rs_Duration_weeks(self._inner); + return c.temporal_rs_Duration_weeks(self._inner); } /// Get the days component of the duration. pub fn days(self: Duration) i64 { - return temporal_rs_Duration_days(self._inner); + return c.temporal_rs_Duration_days(self._inner); } /// Get the hours component of the duration. pub fn hours(self: Duration) i64 { - return temporal_rs_Duration_hours(self._inner); + return c.temporal_rs_Duration_hours(self._inner); } /// Get the minutes component of the duration. pub fn minutes(self: Duration) i64 { - return temporal_rs_Duration_minutes(self._inner); + return c.temporal_rs_Duration_minutes(self._inner); } /// Get the seconds component of the duration. pub fn seconds(self: Duration) i64 { - return temporal_rs_Duration_seconds(self._inner); + return c.temporal_rs_Duration_seconds(self._inner); } /// Get the milliseconds component of the duration. pub fn milliseconds(self: Duration) i64 { - return temporal_rs_Duration_milliseconds(self._inner); + return c.temporal_rs_Duration_milliseconds(self._inner); } /// Get the microseconds component of the duration. pub fn microseconds(self: Duration) f64 { - return temporal_rs_Duration_microseconds(self._inner); + return c.temporal_rs_Duration_microseconds(self._inner); } /// Get the nanoseconds component of the duration. pub fn nanoseconds(self: Duration) f64 { - return temporal_rs_Duration_nanoseconds(self._inner); + return c.temporal_rs_Duration_nanoseconds(self._inner); } /// Get the sign of the duration: positive (1), zero (0), or negative (-1). pub fn sign(self: Duration) Sign { - return temporal_rs_Duration_sign(self._inner); + return @as(Sign, @enumFromInt(c.temporal_rs_Duration_sign(self._inner))); } /// Check if the duration is zero (all fields are zero). pub fn blank(self: Duration) bool { - return temporal_rs_Duration_is_zero(self._inner); + return c.temporal_rs_Duration_is_zero(self._inner); } /// Returns a new Duration with the absolute value (all components positive). pub fn abs(self: Duration) Duration { - const ptr = temporal_rs_Duration_abs(self._inner); + const ptr: *c.Duration = c.temporal_rs_Duration_abs(self._inner) orelse unreachable; return .{ ._inner = ptr }; } /// Returns a new Duration with all components negated. pub fn negated(self: Duration) Duration { - const ptr = temporal_rs_Duration_negated(self._inner); + const ptr: *c.Duration = c.temporal_rs_Duration_negated(self._inner) orelse unreachable; return .{ ._inner = ptr }; } /// Add two durations together (Temporal.Duration.prototype.add). pub fn add(self: Duration, other: Duration) !Duration { - return wrapDuration(temporal_rs_Duration_add(self._inner, other._inner)); + return wrapDuration(c.temporal_rs_Duration_add(self._inner, other._inner)); } /// Subtract another duration from this one (Temporal.Duration.prototype.subtract). pub fn subtract(self: Duration, other: Duration) !Duration { - return wrapDuration(temporal_rs_Duration_subtract(self._inner, other._inner)); + return wrapDuration(c.temporal_rs_Duration_subtract(self._inner, other._inner)); } /// Round the duration according to the specified options (Temporal.Duration.prototype.round). pub fn round(self: Duration, options: RoundingOptions, relative_to: RelativeTo) !Duration { - return wrapDuration(temporal_rs_Duration_round(self._inner, options, relative_to)); + return wrapDuration(c.temporal_rs_Duration_round(self._inner, options, relative_to)); } /// Round the duration with an explicit provider. -fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: RelativeTo, provider: *const Provider) !Duration { - return wrapDuration(temporal_rs_Duration_round_with_provider(self._inner, options, relative_to, provider)); +fn roundWithProvider(self: Duration, options: RoundingOptions, relative_to: RelativeTo, provider: *const c.Provider) !Duration { + return wrapDuration(c.temporal_rs_Duration_round_with_provider(self._inner, options, relative_to, provider)); } /// Compare two durations (Temporal.Duration.compare). pub fn compare(self: Duration, other: Duration, relative_to: RelativeTo) !i8 { - const res = temporal_rs_Duration_compare(self._inner, other._inner, relative_to); + const res = c.temporal_rs_Duration_compare(self._inner, other._inner, relative_to); if (!res.is_ok) return error.TemporalError; - return res.result.ok; + return res.unnamed_0.ok; } /// Compare two durations with an explicit provider. -fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const Provider) !i8 { - const res = temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider); +fn compareWithProvider(self: Duration, other: Duration, relative_to: RelativeTo, provider: *const c.Provider) !i8 { + const res = c.temporal_rs_Duration_compare_with_provider(self._inner, other._inner, relative_to, provider); if (!res.is_ok) return error.TemporalError; - return res.result.ok; + return res.unnamed_0.ok; } /// Get the total value of the duration in the specified unit (Temporal.Duration.prototype.total). pub fn total(self: Duration, unit: Unit, relative_to: RelativeTo) !f64 { - const res = temporal_rs_Duration_total(self._inner, unit, relative_to); + const res = c.temporal_rs_Duration_total(self._inner, @intFromEnum(unit), relative_to); if (!res.is_ok) return error.TemporalError; - return res.result.ok; + return res.unnamed_0.ok; } /// Get the total value of the duration with an explicit provider. -fn totalWithProvider(self: Duration, unit: Unit, relative_to: RelativeTo, provider: *const Provider) !f64 { - const res = temporal_rs_Duration_total_with_provider(self._inner, unit, relative_to, provider); +fn totalWithProvider(self: Duration, unit: Unit, relative_to: RelativeTo, provider: *const c.Provider) !f64 { + const res = c.temporal_rs_Duration_total_with_provider(self._inner, @intFromEnum(unit), relative_to, provider); if (!res.is_ok) return error.TemporalError; - return res.result.ok; + return res.unnamed_0.ok; } /// Convert to string (Temporal.Duration.prototype.toString); caller owns returned slice. pub fn toString(self: Duration, allocator: std.mem.Allocator, options: ToStringRoundingOptions) ![]u8 { - const writer = diplomat_buffer_write_create(128); - defer diplomat_buffer_write_destroy(writer); + const writer = c.diplomat_buffer_write_create(128); + defer c.diplomat_buffer_write_destroy(writer); - const res = temporal_rs_Duration_to_string(self._inner, options, writer); + const res = c.temporal_rs_Duration_to_string(self._inner, options, writer); try handleVoidResult(res); - const len = diplomat_buffer_write_len(writer); - const source = diplomat_buffer_write_get_bytes(writer)[0..len]; + const len = c.diplomat_buffer_write_len(writer); + const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; const out = try allocator.alloc(u8, len); std.mem.copyForwards(u8, out, source); @@ -335,32 +337,30 @@ pub fn toLocaleString(self: Duration, allocator: std.mem.Allocator) ![]u8 { /// Clone the underlying duration. fn clone(self: Duration) Duration { - const ptr = temporal_rs_Duration_clone(self._inner); + const ptr: *c.Duration = c.temporal_rs_Duration_clone(self._inner) orelse unreachable; return .{ ._inner = ptr }; } pub fn deinit(self: Duration) void { - temporal_rs_Duration_destroy(self._inner); + c.temporal_rs_Duration_destroy(self._inner); } // --- Helpers ----------------------------------------------------------------- -fn wrapDuration(res: DurationResult) !Duration { +fn handleVoidResult(res: anytype) !void { if (!res.is_ok) return error.TemporalError; - const ptr = res.result.ok orelse return error.TemporalError; +} + +fn wrapDuration(res: anytype) !Duration { + if (!res.is_ok) return error.TemporalError; + const maybe_ptr = res.unnamed_0.ok; + if (maybe_ptr == null) return error.TemporalError; + const ptr: *c.Duration = maybe_ptr.?; return .{ ._inner = ptr }; } -fn handleVoidResult(res: VoidResult) !void { - if (!res.is_ok) return error.TemporalError; -} - fn defaultToStringRoundingOptions() ToStringRoundingOptions { - return .{ - .precision = .{ .is_minute = false, .precision = OptionU8{ .ok = 0, .is_ok = false } }, - .smallest_unit = Unit_option{ .ok = .auto, .is_ok = false }, - .rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = false }, - }; + return temporal_rs.to_string_rounding_options_auto; } // --- Public helper types ----------------------------------------------------- @@ -379,277 +379,71 @@ pub const ToStringOptions = struct { smallest_unit: ?Unit = null, }; -/// A partial duration where some or all fields may be omitted. -pub const PartialDuration = extern struct { - years: OptionI64, - months: OptionI64, - weeks: OptionI64, - days: OptionI64, - hours: OptionI64, - minutes: OptionI64, - seconds: OptionI64, - milliseconds: OptionI64, - microseconds: OptionI128, - nanoseconds: OptionI128, - - pub fn empty() PartialDuration { - return .{ - .years = .{ .ok = 0, .is_ok = false }, - .months = .{ .ok = 0, .is_ok = false }, - .weeks = .{ .ok = 0, .is_ok = false }, - .days = .{ .ok = 0, .is_ok = false }, - .hours = .{ .ok = 0, .is_ok = false }, - .minutes = .{ .ok = 0, .is_ok = false }, - .seconds = .{ .ok = 0, .is_ok = false }, - .milliseconds = .{ .ok = 0, .is_ok = false }, - .microseconds = .{ .ok = 0, .is_ok = false }, - .nanoseconds = .{ .ok = 0, .is_ok = false }, - }; - } - - pub fn withYears(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.years = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withMonths(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.months = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withWeeks(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.weeks = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withDays(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.days = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withHours(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.hours = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withMinutes(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.minutes = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withSeconds(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.seconds = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withMilliseconds(self: PartialDuration, value: i64) PartialDuration { - var result = self; - result.milliseconds = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withMicroseconds(self: PartialDuration, value: i128) PartialDuration { - var result = self; - result.microseconds = .{ .ok = value, .is_ok = true }; - return result; - } - - pub fn withNanoseconds(self: PartialDuration, value: i128) PartialDuration { - var result = self; - result.nanoseconds = .{ .ok = value, .is_ok = true }; - return result; - } -}; +pub const PartialDuration = c.PartialDuration; /// Relative-to context for duration operations. pub const RelativeTo = extern struct { - plain_date: ?*PlainDate, - zoned_date_time: ?*ZonedDateTime, + plain_date: ?*c.PlainDate, + zoned_date_time: ?*c.ZonedDateTime, }; -// --- Extern types ------------------------------------------------------------ +// --- Public type aliases and enums ------------------------------------------ -const CDuration = opaque {}; -const PlainDate = opaque {}; -const ZonedDateTime = opaque {}; -const Provider = opaque {}; - -const DiplomatStringView = extern struct { data: [*c]const u8, len: usize }; -const DiplomatString16View = extern struct { data: [*c]const u16, len: usize }; - -const OptionStringView = extern struct { ok: DiplomatStringView, is_ok: bool }; -const OptionU8 = extern struct { ok: u8, is_ok: bool }; -const OptionU32 = extern struct { ok: u32, is_ok: bool }; -const OptionI64 = extern struct { ok: i64, is_ok: bool }; -const OptionI128 = extern struct { ok: i128, is_ok: bool }; - -const DiplomatWrite = extern struct { - context: ?*anyopaque, - buf: [*c]u8, - len: usize, - cap: usize, - grow_failed: bool, - flush: ?*const fn (*DiplomatWrite) void, - grow: ?*const fn (*DiplomatWrite, usize) bool, -}; - -const Precision = extern struct { - is_minute: bool, - precision: OptionU8, -}; +const OptionU8 = c.OptionU8; +const OptionU32 = c.OptionU32; +const OptionI64 = c.OptionI64; +const OptionF64 = c.OptionF64; +const Precision = c.Precision; +const Unit_option = c.Unit_option; +const RoundingMode_option = c.RoundingMode_option; /// Time unit for Temporal operations. /// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal -pub const Unit = enum(c_int) { - auto = 0, - nanosecond = 1, - microsecond = 2, - millisecond = 3, - second = 4, - minute = 5, - hour = 6, - day = 7, - week = 8, - month = 9, - year = 10, +pub const Unit = enum(c_uint) { + auto = c.Unit_Auto, + nanosecond = c.Unit_Nanosecond, + microsecond = c.Unit_Microsecond, + millisecond = c.Unit_Millisecond, + second = c.Unit_Second, + minute = c.Unit_Minute, + hour = c.Unit_Hour, + day = c.Unit_Day, + week = c.Unit_Week, + month = c.Unit_Month, + year = c.Unit_Year, }; -const Unit_option = extern struct { ok: Unit, is_ok: bool }; - /// Rounding mode for Temporal operations. /// See: https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers -pub const RoundingMode = enum(c_int) { +pub const RoundingMode = enum(c_uint) { /// Round toward positive infinity - ceil = 0, + ceil = c.RoundingMode_Ceil, /// Round toward negative infinity - floor = 1, + floor = c.RoundingMode_Floor, /// Round away from zero - expand = 2, + expand = c.RoundingMode_Expand, /// Round toward zero (truncate) - trunc = 3, + trunc = c.RoundingMode_Trunc, /// Round half toward positive infinity - half_ceil = 4, + half_ceil = c.RoundingMode_HalfCeil, /// Round half toward negative infinity - half_floor = 5, + half_floor = c.RoundingMode_HalfFloor, /// Round half away from zero - half_expand = 6, + half_expand = c.RoundingMode_HalfExpand, /// Round half toward zero - half_trunc = 7, + half_trunc = c.RoundingMode_HalfTrunc, /// Round half to even (banker's rounding) - half_even = 8, -}; - -const RoundingMode_option = extern struct { ok: RoundingMode, is_ok: bool }; - -pub const RoundingOptions = extern struct { - largest_unit: Unit_option, - smallest_unit: Unit_option, - rounding_mode: RoundingMode_option, - increment: OptionU32, -}; - -pub const ToStringRoundingOptions = extern struct { - precision: Precision, - smallest_unit: Unit_option, - rounding_mode: RoundingMode_option, -}; - -const ErrorKind = enum(c_int) { - ErrorKind_Generic = 0, - ErrorKind_Type = 1, - ErrorKind_Range = 2, - ErrorKind_Syntax = 3, - ErrorKind_Assert = 4, -}; - -const TemporalError = extern struct { - kind: ErrorKind, - msg: OptionStringView, + half_even = c.RoundingMode_HalfEven, }; pub const Sign = enum(c_int) { - Sign_Positive = 1, - Sign_Zero = 0, - Sign_Negative = -1, + positive = c.Sign_Positive, + zero = c.Sign_Zero, + negative = c.Sign_Negative, }; -// --- Result wrappers --------------------------------------------------------- - -const DurationResult = extern struct { - result: extern union { - ok: ?*CDuration, - err: TemporalError, - }, - is_ok: bool, -}; - -const I8Result = extern struct { - result: extern union { - ok: i8, - err: TemporalError, - }, - is_ok: bool, -}; - -const F64Result = extern struct { - result: extern union { - ok: f64, - err: TemporalError, - }, - is_ok: bool, -}; - -const VoidResult = extern struct { - result: extern union { - err: TemporalError, - }, - is_ok: bool, -}; - -// --- Extern functions ------------------------------------------------------- - -extern "c" fn temporal_rs_Duration_try_new(years: i64, months: i64, weeks: i64, days: i64, hours: i64, minutes: i64, seconds: i64, milliseconds: i64, microseconds: f64, nanoseconds: f64) DurationResult; -extern "c" fn temporal_rs_Duration_from_partial_duration(partial: PartialDuration) DurationResult; -extern "c" fn temporal_rs_Duration_from_utf8(s: DiplomatStringView) DurationResult; -extern "c" fn temporal_rs_Duration_from_utf16(s: DiplomatString16View) DurationResult; -extern "c" fn temporal_rs_Duration_is_time_within_range(self: *const CDuration) bool; -extern "c" fn temporal_rs_Duration_years(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_months(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_weeks(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_days(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_hours(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_minutes(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_seconds(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_milliseconds(self: *const CDuration) i64; -extern "c" fn temporal_rs_Duration_microseconds(self: *const CDuration) f64; -extern "c" fn temporal_rs_Duration_nanoseconds(self: *const CDuration) f64; -extern "c" fn temporal_rs_Duration_sign(self: *const CDuration) Sign; -extern "c" fn temporal_rs_Duration_is_zero(self: *const CDuration) bool; -extern "c" fn temporal_rs_Duration_abs(self: *const CDuration) *CDuration; -extern "c" fn temporal_rs_Duration_negated(self: *const CDuration) *CDuration; -extern "c" fn temporal_rs_Duration_add(self: *const CDuration, other: *const CDuration) DurationResult; -extern "c" fn temporal_rs_Duration_subtract(self: *const CDuration, other: *const CDuration) DurationResult; -extern "c" fn temporal_rs_Duration_to_string(self: *const CDuration, options: ToStringRoundingOptions, write: *DiplomatWrite) VoidResult; -extern "c" fn temporal_rs_Duration_round(self: *const CDuration, options: RoundingOptions, relative_to: RelativeTo) DurationResult; -extern "c" fn temporal_rs_Duration_round_with_provider(self: *const CDuration, options: RoundingOptions, relative_to: RelativeTo, p: *const Provider) DurationResult; -extern "c" fn temporal_rs_Duration_compare(self: *const CDuration, other: *const CDuration, relative_to: RelativeTo) I8Result; -extern "c" fn temporal_rs_Duration_compare_with_provider(self: *const CDuration, other: *const CDuration, relative_to: RelativeTo, p: *const Provider) I8Result; -extern "c" fn temporal_rs_Duration_total(self: *const CDuration, unit: Unit, relative_to: RelativeTo) F64Result; -extern "c" fn temporal_rs_Duration_total_with_provider(self: *const CDuration, unit: Unit, relative_to: RelativeTo, p: *const Provider) F64Result; -extern "c" fn temporal_rs_Duration_clone(self: *const CDuration) *CDuration; -extern "c" fn temporal_rs_Duration_destroy(self: *CDuration) void; - -extern "c" fn diplomat_buffer_write_create(cap: usize) *DiplomatWrite; -extern "c" fn diplomat_buffer_write_get_bytes(write: *DiplomatWrite) [*c]u8; -extern "c" fn diplomat_buffer_write_len(write: *DiplomatWrite) usize; -extern "c" fn diplomat_buffer_write_destroy(write: *DiplomatWrite) void; +pub const RoundingOptions = c.RoundingOptions; +pub const ToStringRoundingOptions = c.ToStringRoundingOptions; // --- Tests ------------------------------------------------------------------- @@ -689,7 +483,7 @@ test from { defer dur_negative.deinit(); try std.testing.expectEqual(@as(i64, -1), dur_negative.days()); - try std.testing.expectEqual(Sign.Sign_Negative, dur_negative.sign()); + try std.testing.expectEqual(Sign.negative, dur_negative.sign()); } test blank { @@ -697,7 +491,7 @@ test blank { defer dur.deinit(); try std.testing.expect(dur.blank()); - try std.testing.expectEqual(Sign.Sign_Zero, dur.sign()); + try std.testing.expectEqual(Sign.zero, dur.sign()); } test add { @@ -735,7 +529,7 @@ test abs { try std.testing.expectEqual(@as(i64, 1), abs_dur.hours()); try std.testing.expectEqual(@as(i64, 30), abs_dur.minutes()); - try std.testing.expectEqual(Sign.Sign_Positive, abs_dur.sign()); + try std.testing.expectEqual(Sign.positive, abs_dur.sign()); } test negated { @@ -746,7 +540,7 @@ test negated { defer neg.deinit(); try std.testing.expectEqual(@as(i64, -1), neg.hours()); - try std.testing.expectEqual(Sign.Sign_Negative, neg.sign()); + try std.testing.expectEqual(Sign.negative, neg.sign()); } test toString { @@ -762,9 +556,21 @@ test toString { } test fromPartialDuration { - var partial = PartialDuration.empty(); - partial = partial.withHours(3); - partial = partial.withMinutes(45); + const empty_i64 = temporal_rs.toOption(c.OptionI64, null); + const empty_f64 = temporal_rs.toOption(c.OptionF64, null); + + const partial = c.PartialDuration{ + .years = empty_i64, + .months = empty_i64, + .weeks = empty_i64, + .days = empty_i64, + .hours = .{ .is_ok = true, .unnamed_0 = .{ .ok = 3 } }, + .minutes = .{ .is_ok = true, .unnamed_0 = .{ .ok = 45 } }, + .seconds = empty_i64, + .milliseconds = empty_i64, + .microseconds = empty_f64, + .nanoseconds = empty_f64, + }; const dur = try Duration.fromPartialDuration(partial); defer dur.deinit(); diff --git a/src/Instant.zig b/src/Instant.zig index 951875a..89f048e 100644 --- a/src/Instant.zig +++ b/src/Instant.zig @@ -1,11 +1,61 @@ const std = @import("std"); +const temporal_rs = @import("cabi.zig"); +const c = temporal_rs.c; const Instant = @This(); -_inner: *CInstant, +_inner: *c.Instant, epoch_milliseconds: i64, epoch_nanoseconds: i128, +pub const Unit = enum(c_uint) { + auto = c.Unit_Auto, + nanosecond = c.Unit_Nanosecond, + microsecond = c.Unit_Microsecond, + millisecond = c.Unit_Millisecond, + second = c.Unit_Second, + minute = c.Unit_Minute, + hour = c.Unit_Hour, + day = c.Unit_Day, + week = c.Unit_Week, + month = c.Unit_Month, + year = c.Unit_Year, +}; + +pub const RoundingMode = enum(c_uint) { + ceil = c.RoundingMode_Ceil, + floor = c.RoundingMode_Floor, + expand = c.RoundingMode_Expand, + trunc = c.RoundingMode_Trunc, + half_ceil = c.RoundingMode_HalfCeil, + half_floor = c.RoundingMode_HalfFloor, + half_expand = c.RoundingMode_HalfExpand, + half_trunc = c.RoundingMode_HalfTrunc, + half_even = c.RoundingMode_HalfEven, +}; +pub const Sign = enum(c_int) { + positive = c.Sign_Positive, + zero = c.Sign_Zero, + negative = c.Sign_Negative, +}; +const TimeZone = c.TimeZone; +const TimeZone_option = c.TimeZone_option; +const Precision = c.Precision; +const Unit_option = c.Unit_option; +const RoundingMode_option = c.RoundingMode_option; +const DifferenceSettings = c.DifferenceSettings; +const RoundingOptions = c.RoundingOptions; +const ToStringRoundingOptions = c.ToStringRoundingOptions; +const OptionU8 = c.OptionU8; +const OptionU32 = c.OptionU32; +const TemporalError = c.TemporalError; +const DiplomatStringView = c.DiplomatStringView; +const DiplomatString16View = c.DiplomatString16View; +const DiplomatWrite = c.DiplomatWrite; +const Duration = c.Duration; +const ZonedDateTime = c.ZonedDateTime; +const Provider = c.Provider; + /// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds). pub fn init(epoch_ns: i128) !Instant { return fromEpochNanoseconds(epoch_ns); @@ -13,79 +63,75 @@ pub fn init(epoch_ns: i128) !Instant { /// Construct from epoch milliseconds. pub fn fromEpochMilliseconds(epoch_ms: i64) !Instant { - return wrapInstant(temporal_rs_Instant_from_epoch_milliseconds(epoch_ms)); + return wrapInstant(c.temporal_rs_Instant_from_epoch_milliseconds(epoch_ms)); } /// Construct from epoch nanoseconds (Temporal.Instant.fromEpochNanoseconds). pub fn fromEpochNanoseconds(epoch_ns: i128) !Instant { - const parts = i128ToParts(epoch_ns); - return wrapInstant(temporal_rs_Instant_try_new(parts)); + const parts = temporal_rs.toI128Nanoseconds(epoch_ns); + return wrapInstant(c.temporal_rs_Instant_try_new(parts)); } /// Parse an ISO 8601 string (Temporal.Instant.from). pub fn from(text: []const u8) !Instant { - const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; - return wrapInstant(temporal_rs_Instant_from_utf8(view)); + const view = temporal_rs.toDiplomatStringView(text); + return wrapInstant(c.temporal_rs_Instant_from_utf8(view)); } /// Parse an ISO 8601 UTF-16 string (Temporal.Instant.from). fn fromUtf16(text: []const u16) !Instant { - const view = DiplomatString16View{ .data = text.ptr, .len = text.len }; - return wrapInstant(temporal_rs_Instant_from_utf16(view)); + const view = temporal_rs.toDiplomatString16View(text); + return wrapInstant(c.temporal_rs_Instant_from_utf16(view)); } /// Add a Duration to this instant (Temporal.Instant.prototype.add). pub fn add(self: Instant, duration: *const Duration) !Instant { - return wrapInstant(temporal_rs_Instant_add(self._inner, duration)); + return wrapInstant(c.temporal_rs_Instant_add(self._inner, duration)); } /// Subtract a Duration from this instant (Temporal.Instant.prototype.subtract). pub fn subtract(self: Instant, duration: *const Duration) !Instant { - return wrapInstant(temporal_rs_Instant_subtract(self._inner, duration)); + return wrapInstant(c.temporal_rs_Instant_subtract(self._inner, duration)); } /// Difference until another instant (Temporal.Instant.prototype.until). pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { - return wrapDuration(temporal_rs_Instant_until(self._inner, other._inner, settings)); + return wrapDuration(c.temporal_rs_Instant_until(self._inner, other._inner, settings)); } /// Difference since another instant (Temporal.Instant.prototype.since). pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { - return wrapDuration(temporal_rs_Instant_since(self._inner, other._inner, settings)); + return wrapDuration(c.temporal_rs_Instant_since(self._inner, other._inner, settings)); } /// Round this instant (Temporal.Instant.prototype.round). pub fn round(self: Instant, options: RoundingOptions) !Instant { - return wrapInstant(temporal_rs_Instant_round(self._inner, options)); + return wrapInstant(c.temporal_rs_Instant_round(self._inner, options)); } /// Compare two instants (Temporal.Instant.compare). pub fn compare(a: Instant, b: Instant) i8 { - return temporal_rs_Instant_compare(a._inner, b._inner); + return c.temporal_rs_Instant_compare(a._inner, b._inner); } /// Equality check (Temporal.Instant.prototype.equals). pub fn equals(a: Instant, b: Instant) bool { - return temporal_rs_Instant_equals(a._inner, b._inner); + return c.temporal_rs_Instant_equals(a._inner, b._inner); } /// Convert to string using compiled TZ data; caller owns returned slice. pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptions) ![]u8 { - const zone_opt = if (opts.time_zone) |z| - TimeZone_option{ .ok = z, .is_ok = true } - else - TimeZone_option{ .ok = undefined, .is_ok = false }; - + const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone); const rounding = optsToRounding(opts); - const writer = diplomat_buffer_write_create(128); - defer diplomat_buffer_write_destroy(writer); + const writer = c.diplomat_buffer_write_create(128); + defer c.diplomat_buffer_write_destroy(writer); - const res = temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self._inner, zone_opt, rounding, writer); + const res = c.temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self._inner, zone_opt, rounding, writer); try handleVoidResult(res); - const len = diplomat_buffer_write_len(writer); - const source = diplomat_buffer_write_get_bytes(writer)[0..len]; + const len = c.diplomat_buffer_write_len(writer); + const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; const out = try allocator.alloc(u8, len); std.mem.copyForwards(u8, out, source); @@ -94,21 +140,17 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio /// Convert to string using an explicit provider. fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: *const Provider, opts: ToStringOptions) ![]u8 { - const zone_opt = if (opts.time_zone) |z| - TimeZone_option{ .ok = z, .is_ok = true } - else - TimeZone_option{ .ok = undefined, .is_ok = false }; - + const zone_opt = temporal_rs.toTimeZoneOption(opts.time_zone); const rounding = optsToRounding(opts); - const writer = diplomat_buffer_write_create(128); - defer diplomat_buffer_write_destroy(writer); + const writer = c.diplomat_buffer_write_create(128); + defer c.diplomat_buffer_write_destroy(writer); - const res = temporal_rs_Instant_to_ixdtf_string_with_provider(self._inner, zone_opt, rounding, provider, writer); + const res = c.temporal_rs_Instant_to_ixdtf_string_with_provider(self._inner, zone_opt, rounding, provider, writer); try handleVoidResult(res); - const len = diplomat_buffer_write_len(writer); - const source = diplomat_buffer_write_get_bytes(writer)[0..len]; + const len = c.diplomat_buffer_write_len(writer); + const source = c.diplomat_buffer_write_get_bytes(writer)[0..len]; const out = try allocator.alloc(u8, len); std.mem.copyForwards(u8, out, source); @@ -127,49 +169,49 @@ pub fn toLocaleString(self: Instant, allocator: std.mem.Allocator) ![]u8 { /// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO). pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTimeHandle { - return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone)); + return wrapZonedDateTime(c.temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone)); } /// Convert to ZonedDateTime using an explicit provider. fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const Provider) !ZonedDateTimeHandle { - return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider)); + return wrapZonedDateTime(c.temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider)); } /// Clone the underlying instant. fn clone(self: Instant) Instant { - const ptr = temporal_rs_Instant_clone(self._inner); - return .{ ._inner = ptr, .epoch_milliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; + const ptr = c.temporal_rs_Instant_clone(self._inner) orelse unreachable; + return .{ ._inner = ptr, .epoch_milliseconds = c.temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = temporal_rs.fromI128Nanoseconds(c.temporal_rs_Instant_epoch_nanoseconds(ptr)) }; } pub fn deinit(self: Instant) void { - temporal_rs_Instant_destroy(self._inner); + c.temporal_rs_Instant_destroy(self._inner); } // --- Helpers ----------------------------------------------------------------- -fn wrapInstant(res: InstantResult) !Instant { +fn wrapInstant(res: anytype) !Instant { if (!res.is_ok) return error.TemporalError; - const ptr = res.result.ok orelse return error.TemporalError; - return .{ ._inner = ptr, .epoch_milliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; + const maybe_ptr = res.unnamed_0.ok; + if (maybe_ptr == null) return error.TemporalError; + const ptr: *c.Instant = maybe_ptr.?; + return .{ ._inner = ptr, .epoch_milliseconds = c.temporal_rs_Instant_epoch_milliseconds(ptr), .epoch_nanoseconds = temporal_rs.fromI128Nanoseconds(c.temporal_rs_Instant_epoch_nanoseconds(ptr)) }; } -fn wrapDuration(res: DurationResult) !DurationHandle { +fn wrapDuration(res: anytype) !DurationHandle { if (!res.is_ok) return error.TemporalError; - const ptr = res.result.ok orelse return error.TemporalError; - return .{ .ptr = ptr }; + return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError }; } -fn wrapZonedDateTime(res: ZonedDateTimeResult) !ZonedDateTimeHandle { +fn wrapZonedDateTime(res: anytype) !ZonedDateTimeHandle { if (!res.is_ok) return error.TemporalError; - const ptr = res.result.ok orelse return error.TemporalError; - return .{ .ptr = ptr }; + return .{ .ptr = res.unnamed_0.ok orelse return error.TemporalError }; } -fn handleVoidResult(res: VoidResult) !void { +fn handleVoidResult(res: anytype) !void { if (!res.is_ok) return error.TemporalError; } -fn i128ToParts(value: i128) I128Nanoseconds { +fn i128ToParts(value: i128) temporal_rs.c.I128Nanoseconds { const is_neg = value < 0; const mag: u128 = if (is_neg) @intCast(@as(u128, @intCast(-value))) else @intCast(value); const mask: u64 = 1 << 63; @@ -179,7 +221,7 @@ fn i128ToParts(value: i128) I128Nanoseconds { return .{ .high = high, .low = low }; } -fn partsToI128(value: I128Nanoseconds) i128 { +fn partsToI128(value: temporal_rs.c.I128Nanoseconds) i128 { const mask: u64 = 1 << 63; const is_neg = (value.high & mask) != 0; const mag: u128 = ((@as(u128, value.high & ~mask)) << 64) | value.low; @@ -188,48 +230,34 @@ fn partsToI128(value: I128Nanoseconds) i128 { } fn defaultPrecision() Precision { - return .{ .is_minute = false, .precision = OptionU8{ .ok = 0, .is_ok = false } }; + return .{ .is_minute = false, .precision = temporal_rs.toOption(c.OptionU8, null) }; } fn defaultToStringRoundingOptions() ToStringRoundingOptions { - return .{ - .precision = defaultPrecision(), - .smallest_unit = Unit_option{ .ok = .auto, .is_ok = false }, - .rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = false }, - }; + return temporal_rs.to_string_rounding_options_auto; } /// Convert ToStringOptions to ToStringRoundingOptions for the C API fn optsToRounding(opts: ToStringOptions) ToStringRoundingOptions { // If smallest_unit is specified, use it; otherwise use fractional_second_digits for precision - const smallest_unit_opt = if (opts.smallest_unit) |unit| - Unit_option{ .ok = unit, .is_ok = true } - else - Unit_option{ .ok = .auto, .is_ok = false }; - const precision = if (opts.fractional_second_digits) |digits| - Precision{ .is_minute = false, .precision = OptionU8{ .ok = digits, .is_ok = true } } + Precision{ .is_minute = false, .precision = temporal_rs.toOption(c.OptionU8, digits) } else defaultPrecision(); - const rounding_mode_opt = if (opts.rounding_mode) |mode| - RoundingMode_option{ .ok = mode, .is_ok = true } - else - RoundingMode_option{ .ok = .trunc, .is_ok = false }; + const smallest_unit = if (opts.smallest_unit) |unit| @intFromEnum(unit) else null; + const rounding_mode = if (opts.rounding_mode) |mode| @intFromEnum(mode) else null; return .{ .precision = precision, - .smallest_unit = smallest_unit_opt, - .rounding_mode = rounding_mode_opt, + .smallest_unit = temporal_rs.toUnitOption(smallest_unit), + .rounding_mode = temporal_rs.toRoundingModeOption(rounding_mode), }; } fn parseDuration(text: []const u8) !DurationHandle { - const view = DiplomatStringView{ .data = text.ptr, .len = text.len }; - const res = temporal_rs_Duration_from_utf8(view); - if (!res.is_ok) return error.TemporalError; - const ptr = res.result.ok orelse return error.TemporalError; - return .{ .ptr = ptr }; + const view = temporal_rs.toDiplomatStringView(text); + return wrapDuration(c.temporal_rs_Duration_from_utf8(view)); } // --- Public helper types ----------------------------------------------------- @@ -260,7 +288,7 @@ const DurationHandle = struct { ptr: *Duration, pub fn deinit(self: DurationHandle) void { - temporal_rs_Duration_destroy(self.ptr); + c.temporal_rs_Duration_destroy(self.ptr); } }; @@ -268,215 +296,10 @@ const ZonedDateTimeHandle = struct { ptr: *ZonedDateTime, pub fn deinit(self: ZonedDateTimeHandle) void { - temporal_rs_ZonedDateTime_destroy(self.ptr); + c.temporal_rs_ZonedDateTime_destroy(self.ptr); } }; -// --- Extern types ------------------------------------------------------------ - -const CInstant = opaque {}; -const Duration = opaque {}; -const ZonedDateTime = opaque {}; -const Provider = opaque {}; - -const I128Nanoseconds = extern struct { high: u64, low: u64 }; -const I128Nanoseconds_option = extern struct { ok: I128Nanoseconds, is_ok: bool }; - -const DiplomatStringView = extern struct { data: [*c]const u8, len: usize }; -const DiplomatString16View = extern struct { data: [*c]const u16, len: usize }; - -const OptionStringView = extern struct { ok: DiplomatStringView, is_ok: bool }; -const OptionU8 = extern struct { ok: u8, is_ok: bool }; -const OptionU32 = extern struct { ok: u32, is_ok: bool }; - -const DiplomatWrite = extern struct { - context: ?*anyopaque, - buf: [*c]u8, - len: usize, - cap: usize, - grow_failed: bool, - flush: ?*const fn (*DiplomatWrite) void, - grow: ?*const fn (*DiplomatWrite, usize) bool, -}; - -const TimeZone = extern struct { - offset_minutes: i16, - resolved_id: usize, - normalized_id: usize, - is_iana_id: bool, -}; - -const TimeZone_option = extern struct { - ok: TimeZone, - is_ok: bool, -}; - -const Precision = extern struct { - is_minute: bool, - precision: OptionU8, -}; - -/// Time unit for Temporal operations. -/// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal -pub const Unit = enum(c_int) { - auto = 0, - nanosecond = 1, - microsecond = 2, - millisecond = 3, - second = 4, - minute = 5, - hour = 6, - day = 7, - week = 8, - month = 9, - year = 10, -}; - -const Unit_option = extern struct { ok: Unit, is_ok: bool }; - -/// Rounding mode for Temporal operations. -/// See: https://tc39.es/ecma402/#table-sanctioned-single-unit-identifiers -pub const RoundingMode = enum(c_int) { - /// Round toward positive infinity - ceil = 0, - /// Round toward negative infinity - floor = 1, - /// Round away from zero - expand = 2, - /// Round toward zero (truncate) - trunc = 3, - /// Round half toward positive infinity - half_ceil = 4, - /// Round half toward negative infinity - half_floor = 5, - /// Round half away from zero - half_expand = 6, - /// Round half toward zero - half_trunc = 7, - /// Round half to even (banker's rounding) - half_even = 8, -}; - -const RoundingMode_option = extern struct { ok: RoundingMode, is_ok: bool }; - -const DifferenceSettings = extern struct { - largest_unit: Unit_option, - smallest_unit: Unit_option, - rounding_mode: RoundingMode_option, - increment: OptionU32, -}; - -const RoundingOptions = extern struct { - largest_unit: Unit_option, - smallest_unit: Unit_option, - rounding_mode: RoundingMode_option, - increment: OptionU32, -}; - -const ToStringRoundingOptions = extern struct { - precision: Precision, - smallest_unit: Unit_option, - rounding_mode: RoundingMode_option, -}; - -const ErrorKind = enum(c_int) { - ErrorKind_Generic = 0, - ErrorKind_Type = 1, - ErrorKind_Range = 2, - ErrorKind_Syntax = 3, - ErrorKind_Assert = 4, -}; - -const TemporalError = extern struct { - kind: ErrorKind, - msg: OptionStringView, -}; - -const Sign = enum(c_int) { - Sign_Positive = 1, - Sign_Zero = 0, - Sign_Negative = -1, -}; - -// --- Result wrappers --------------------------------------------------------- - -const InstantResult = extern struct { - result: extern union { - ok: ?*CInstant, - err: TemporalError, - }, - is_ok: bool, -}; - -const DurationResult = extern struct { - result: extern union { - ok: ?*Duration, - err: TemporalError, - }, - is_ok: bool, -}; - -const DurationParseResult = extern struct { - result: extern union { - ok: ?*Duration, - err: TemporalError, - }, - is_ok: bool, -}; - -const ZonedDateTimeResult = extern struct { - result: extern union { - ok: ?*ZonedDateTime, - err: TemporalError, - }, - is_ok: bool, -}; - -const VoidResult = extern struct { - result: extern union { - err: TemporalError, - }, - is_ok: bool, -}; - -// --- Extern functions ------------------------------------------------------- - -extern "c" fn temporal_rs_Instant_try_new(ns: I128Nanoseconds) InstantResult; -extern "c" fn temporal_rs_Instant_from_epoch_milliseconds(epoch_milliseconds: i64) InstantResult; -extern "c" fn temporal_rs_Instant_from_utf8(s: DiplomatStringView) InstantResult; -extern "c" fn temporal_rs_Instant_from_utf16(s: DiplomatString16View) InstantResult; -extern "c" fn temporal_rs_Instant_add(self: *const CInstant, duration: *const Duration) InstantResult; -extern "c" fn temporal_rs_Instant_subtract(self: *const CInstant, duration: *const Duration) InstantResult; -extern "c" fn temporal_rs_Instant_since(self: *const CInstant, other: *const CInstant, settings: DifferenceSettings) DurationResult; -extern "c" fn temporal_rs_Instant_until(self: *const CInstant, other: *const CInstant, settings: DifferenceSettings) DurationResult; -extern "c" fn temporal_rs_Instant_round(self: *const CInstant, options: RoundingOptions) InstantResult; -extern "c" fn temporal_rs_Instant_compare(self: *const CInstant, other: *const CInstant) i8; -extern "c" fn temporal_rs_Instant_equals(self: *const CInstant, other: *const CInstant) bool; -extern "c" fn temporal_rs_Instant_epoch_milliseconds(self: *const CInstant) i64; -extern "c" fn temporal_rs_Instant_epoch_nanoseconds(self: *const CInstant) I128Nanoseconds; -extern "c" fn temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self: *const CInstant, zone: TimeZone_option, options: ToStringRoundingOptions, write: *DiplomatWrite) VoidResult; -extern "c" fn temporal_rs_Instant_to_ixdtf_string_with_provider(self: *const CInstant, zone: TimeZone_option, options: ToStringRoundingOptions, p: *const Provider, write: *DiplomatWrite) VoidResult; -extern "c" fn temporal_rs_Instant_to_zoned_date_time_iso(self: *const CInstant, zone: TimeZone) ZonedDateTimeResult; -extern "c" fn temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self: *const CInstant, zone: TimeZone, p: *const Provider) ZonedDateTimeResult; -extern "c" fn temporal_rs_Instant_clone(self: *const CInstant) *CInstant; -extern "c" fn temporal_rs_Instant_destroy(self: *CInstant) void; - -extern "c" fn temporal_rs_Duration_destroy(self: *Duration) void; -extern "c" fn temporal_rs_Duration_from_utf8(s: DiplomatStringView) DurationParseResult; -extern "c" fn temporal_rs_Duration_hours(self: *const Duration) i64; -extern "c" fn temporal_rs_Duration_minutes(self: *const Duration) i64; -extern "c" fn temporal_rs_Duration_seconds(self: *const Duration) i64; -extern "c" fn temporal_rs_Duration_milliseconds(self: *const Duration) i64; -extern "c" fn temporal_rs_Duration_microseconds(self: *const Duration) f64; -extern "c" fn temporal_rs_Duration_nanoseconds(self: *const Duration) f64; -extern "c" fn temporal_rs_Duration_sign(self: *const Duration) Sign; -extern "c" fn temporal_rs_ZonedDateTime_destroy(self: *ZonedDateTime) void; - -extern "c" fn diplomat_buffer_write_create(cap: usize) *DiplomatWrite; -extern "c" fn diplomat_buffer_write_get_bytes(write: *DiplomatWrite) [*c]u8; -extern "c" fn diplomat_buffer_write_len(write: *DiplomatWrite) usize; -extern "c" fn diplomat_buffer_write_destroy(write: *DiplomatWrite) void; - // --- Tests ------------------------------------------------------------------- test init { @@ -544,13 +367,13 @@ test compare { defer a.deinit(); const b = try Instant.fromEpochMilliseconds(0); defer b.deinit(); - const c = try Instant.fromEpochMilliseconds(1_000); - defer c.deinit(); + const cc = try Instant.fromEpochMilliseconds(1_000); + defer cc.deinit(); try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b)); try std.testing.expect(Instant.equals(a, b)); - try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, c)); - try std.testing.expectEqual(@as(i8, 1), Instant.compare(c, a)); + try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, cc)); + try std.testing.expectEqual(@as(i8, 1), Instant.compare(cc, a)); } test equals { @@ -558,13 +381,13 @@ test equals { defer a.deinit(); const b = try Instant.fromEpochMilliseconds(0); defer b.deinit(); - const c = try Instant.fromEpochMilliseconds(1_000); - defer c.deinit(); + const cc = try Instant.fromEpochMilliseconds(1_000); + defer cc.deinit(); try std.testing.expectEqual(@as(i8, 0), Instant.compare(a, b)); try std.testing.expect(Instant.equals(a, b)); - try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, c)); - try std.testing.expectEqual(@as(i8, 1), Instant.compare(c, a)); + try std.testing.expectEqual(@as(i8, -1), Instant.compare(a, cc)); + try std.testing.expectEqual(@as(i8, 1), Instant.compare(cc, a)); } test until { @@ -574,21 +397,21 @@ test until { defer later.deinit(); const settings = DifferenceSettings{ - .largest_unit = Unit_option{ .ok = .hour, .is_ok = true }, - .smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, - .rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = true }, - .increment = OptionU32{ .ok = 0, .is_ok = false }, + .largest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.hour)), + .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)), + .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.trunc)), + .increment = temporal_rs.toOption(c.OptionU32, null), }; var until_handle = try earlier.until(later, settings); defer until_handle.deinit(); - try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(until_handle.ptr)); - try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(until_handle.ptr)); + try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(until_handle.ptr)))); + try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(until_handle.ptr)); var since_handle = try later.since(earlier, settings); defer since_handle.deinit(); - try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(since_handle.ptr)); - try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(since_handle.ptr)); + try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(since_handle.ptr)))); + try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(since_handle.ptr)); } test since { @@ -598,21 +421,21 @@ test since { defer later.deinit(); const settings = DifferenceSettings{ - .largest_unit = Unit_option{ .ok = .hour, .is_ok = true }, - .smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, - .rounding_mode = RoundingMode_option{ .ok = .trunc, .is_ok = true }, - .increment = OptionU32{ .ok = 0, .is_ok = false }, + .largest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.hour)), + .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)), + .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.trunc)), + .increment = temporal_rs.toOption(c.OptionU32, null), }; var until_handle = try earlier.until(later, settings); defer until_handle.deinit(); - try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(until_handle.ptr)); - try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(until_handle.ptr)); + try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(until_handle.ptr)))); + try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(until_handle.ptr)); var since_handle = try later.since(earlier, settings); defer since_handle.deinit(); - try std.testing.expectEqual(Sign.Sign_Positive, temporal_rs_Duration_sign(since_handle.ptr)); - try std.testing.expectEqual(@as(i64, 1), temporal_rs_Duration_hours(since_handle.ptr)); + try std.testing.expectEqual(Sign.positive, @as(Sign, @enumFromInt(temporal_rs.c.temporal_rs_Duration_sign(since_handle.ptr)))); + try std.testing.expectEqual(@as(i64, 1), temporal_rs.c.temporal_rs_Duration_hours(since_handle.ptr)); } test round { @@ -620,10 +443,10 @@ test round { defer inst.deinit(); const opts = RoundingOptions{ - .largest_unit = Unit_option{ .ok = .auto, .is_ok = false }, - .smallest_unit = Unit_option{ .ok = .second, .is_ok = true }, - .rounding_mode = RoundingMode_option{ .ok = .half_expand, .is_ok = true }, - .increment = OptionU32{ .ok = 0, .is_ok = false }, + .largest_unit = temporal_rs.toUnitOption(null), + .smallest_unit = temporal_rs.toUnitOption(@intFromEnum(Unit.second)), + .rounding_mode = temporal_rs.toRoundingModeOption(@intFromEnum(RoundingMode.half_expand)), + .increment = temporal_rs.toOption(c.OptionU32, null), }; const rounded = try inst.round(opts); diff --git a/src/cabi.zig b/src/cabi.zig new file mode 100644 index 0000000..2b01193 --- /dev/null +++ b/src/cabi.zig @@ -0,0 +1,263 @@ +const std = @import("std"); + +pub const c = @cImport({ + @cInclude("AnyCalendarKind.h"); + @cInclude("Calendar.h"); + @cInclude("Duration.h"); + @cInclude("ErrorKind.h"); + @cInclude("I128Nanoseconds.h"); + @cInclude("Instant.h"); + @cInclude("OwnedRelativeTo.h"); + @cInclude("ParsedDate.h"); + @cInclude("ParsedDateTime.h"); + @cInclude("ParsedZonedDateTime.h"); + @cInclude("PlainDate.h"); + @cInclude("PlainDateTime.h"); + @cInclude("PlainMonthDay.h"); + @cInclude("PlainTime.h"); + @cInclude("PlainYearMonth.h"); + @cInclude("RelativeTo.h"); + @cInclude("TimeZone.h"); + @cInclude("ZonedDateTime.h"); +}); + +pub const to_string_rounding_options_auto: c.ToStringRoundingOptions = .{ + .precision = .{ .is_minute = false, .precision = toOption(c.OptionU8, null) }, + .smallest_unit = toUnitOption(null), + .rounding_mode = toRoundingModeOption(null), +}; + +const u64_high_bit_mask: u64 = 1 << 63; + +/// Covert a Rust `I128Nanoseconds` struct to a Zig `i128`. +/// +/// Ported from temporal_rs's [`From`](https://github.com/boa-dev/temporal/blob/89bfca1f5b918d00a19354664e1da11da51305ee/temporal_capi/src/instant.rs#L172-L186) trait for `i128`. +pub fn fromI128Nanoseconds(ns: c.I128Nanoseconds) i128 { + const is_neg = (ns.high & u64_high_bit_mask) != 0; + const ns_high: u128 = @intCast((ns.high & ~u64_high_bit_mask)); + const total: i128 = @intCast((ns_high << 64) + ns.low); + return if (is_neg) -total else total; +} + +/// Covert a Zig `i128` to a Rust `I128Nanoseconds` struct. +/// +/// Ported from temporal_rs's [`From`](https://github.com/boa-dev/temporal/blob/89bfca1f5b918d00a19354664e1da11da51305ee/temporal_capi/src/instant.rs#L188-L207) trait for `ffi::I128Nanoseconds`. +pub fn toI128Nanoseconds(ns: i128) c.I128Nanoseconds { + std.debug.assert(ns != std.math.minInt(i128)); + const is_neg = ns < 0; + const ns_abs = @abs(ns); + const high: u64 = @intCast(ns_abs >> 64); + const low: u64 = @truncate(ns_abs); + return .{ .high = if (is_neg) high | u64_high_bit_mask else high, .low = low }; +} + +/// Convert a Rust `DiplomatStringView` to a Zig slice. +pub fn fromDiplomatStringView(sv: c.DiplomatStringView) []const u8 { + return sv.data[0..sv.len]; +} + +/// Convert a Zig slice to a Rust `DiplomatStringView`. +pub fn toDiplomatStringView(s: []const u8) c.DiplomatStringView { + return .{ .data = s.ptr, .len = s.len }; +} + +/// Convert a Zig slice to a Rust `DiplomatString16View`. +pub fn toDiplomatString16View(s: []const u16) c.DiplomatString16View { + return .{ .data = s.ptr, .len = s.len }; +} + +/// Convert a Rust `Option` to a Zig `?T`. +pub fn fromOption(value: anytype) ?Success(@TypeOf(value)) { + return success(value); +} + +/// Convert a Zig `?T` to a Rust `Option`. +pub fn toOption(comptime T: type, maybe_value: ?Success(T)) T { + return if (maybe_value) |value| + .{ .is_ok = true, .unnamed_0 = .{ .ok = value } } + else + .{ .is_ok = false }; +} + +/// Convert a Zig `?ArithmeticOverflow` to a Rust `Option`. +pub fn toArithmeticOverflowOption(maybe_value: ?c.RoundingMode) c.ArithmeticOverflow_option { + return toOption(c.ArithmeticOverflow_option, maybe_value); +} + +/// Convert a Zig `?Disambiguation` to a Rust `Option`. +pub fn toDisambiguationOption(maybe_value: ?c.Disambiguation) c.Disambiguation_option { + return toOption(c.Disambiguation_option, maybe_value); +} + +/// Convert a Zig `?OffsetDisambiguation` to a Rust `Option`. +pub fn toOffsetDisambiguationOption(maybe_value: ?c.OffsetDisambiguation) c.OffsetDisambiguation_option { + return toOption(c.OffsetDisambiguation_option, maybe_value); +} + +/// Convert a Zig `?PartialDate` to a Rust `Option`. +pub fn toPartialDateOption(maybe_value: ?c.PartialDate) c.PartialDate_option { + return toOption(c.PartialDate_option, maybe_value); +} + +/// Convert a Zig `?RoundingMode` to a Rust `Option`. +pub fn toRoundingModeOption(maybe_value: ?c.RoundingMode) c.RoundingMode_option { + return toOption(c.RoundingMode_option, maybe_value); +} + +/// Convert a Zig `?TimeZone` to a Rust `Option`. +pub fn toTimeZoneOption(maybe_value: ?c.TimeZone) c.TimeZone_option { + return toOption(c.TimeZone_option, maybe_value); +} + +/// Convert a Zig `?Unit` to a Rust `Option`. +pub fn toUnitOption(maybe_value: ?c.Unit) c.Unit_option { + return toOption(c.Unit_option, maybe_value); +} + +// Wraps values from a `c.RelativeTo` or `c.OwnedRelativeTo`. +pub const RelativeTo = union(enum) { + none, + owned_plain_date: *c.PlainDate, + owned_zoned_date_time: *c.ZonedDateTime, + borrowed_plain_date: *const c.PlainDate, + borrowed_zoned_date_time: *const c.ZonedDateTime, + + pub fn fromOwned(owned: c.OwnedRelativeTo) RelativeTo { + if (owned.date) |plain_date| { + return .{ .owned_plain_date = plain_date }; + } else if (owned.zoned) |zoned_date_time| { + return .{ .owned_zoned_date_time = zoned_date_time }; + } else { + return .none; + } + } + + pub fn toRust(self: RelativeTo) c.RelativeTo { + return switch (self) { + .none => .{ .date = null, .zoned = null }, + .owned_plain_date => |plain_date| .{ .date = plain_date, .zoned = null }, + .owned_zoned_date_time => |zoned_date_time| .{ .date = null, .zoned = zoned_date_time }, + .borrowed_plain_date => |plain_date| .{ .date = plain_date, .zoned = null }, + .borrowed_zoned_date_time => |zoned_date_time| .{ .date = null, .zoned = zoned_date_time }, + }; + } + + pub fn deinit(self: RelativeTo) void { + switch (self) { + .owned_plain_date => |plain_date| c.temporal_rs_PlainDate_destroy(plain_date), + .owned_zoned_date_time => |zoned_date_time| c.temporal_rs_ZonedDateTime_destroy(zoned_date_time), + else => {}, + } + } +}; + +pub const DiplomatWrite = struct { + gpa: std.mem.Allocator, + array_list: std.ArrayList(u8), + inner: c.DiplomatWrite, + + pub fn init(gpa: std.mem.Allocator) DiplomatWrite { + return .{ + .gpa = gpa, + .array_list = .empty, + .inner = .{ + // NOTE: We use `@fieldParentPtr()` on the `inner` struct field to get to the other + // fields instead of creating a context externally and storing a pointer. + .context = null, + .buf = undefined, + .len = 0, + .cap = 0, + .grow_failed = false, + .flush = flush, + .grow = grow, + }, + }; + } + + pub fn deinit(self: *DiplomatWrite) void { + self.array_list.deinit(self.gpa); + } + + pub fn toOwnedSlice(self: *DiplomatWrite) std.mem.Allocator.Error![]u8 { + if (self.inner.grow_failed) return error.OutOfMemory; + self.inner = undefined; // Invalidate the inner struct to prevent further writes + return self.array_list.toOwnedSlice(self.gpa); + } + + fn flush(inner: ?*c.DiplomatWrite) callconv(.c) void { + const self: *DiplomatWrite = @fieldParentPtr("inner", inner.?); + self.array_list.items.len = inner.?.len; + } + + fn grow(inner: ?*c.DiplomatWrite, size: usize) callconv(.c) bool { + const self: *DiplomatWrite = @fieldParentPtr("inner", inner.?); + self.array_list.ensureTotalCapacity(self.gpa, size) catch return false; + inner.?.buf = self.array_list.items.ptr; + inner.?.cap = self.array_list.capacity; + return true; + } +}; + +test DiplomatWrite { + const gpa = std.testing.allocator; + var write = DiplomatWrite.init(gpa); + defer write.deinit(); + + const WriteImpl = struct { + inner: *c.DiplomatWrite, + + // https://github.com/rust-diplomat/diplomat/blob/2b903255187976779798fc89df3fee7298641c80/runtime/src/write.rs#L70-L73 + pub fn flush(self: @This()) void { + self.inner.flush.?(self.inner); + } + + // https://github.com/rust-diplomat/diplomat/blob/2b903255187976779798fc89df3fee7298641c80/runtime/src/write.rs#L76-L94 + pub fn writeStr(self: @This(), s: []const u8) void { + if (self.inner.grow_failed) { + return; + } + const needed_len = self.inner.len + s.len; + if (needed_len > self.inner.cap) { + const success_ = self.inner.grow.?(self.inner, needed_len); + if (!success_) { + self.inner.grow_failed = true; + return; + } + } + std.debug.assert(needed_len <= self.inner.cap); + @memcpy(self.inner.buf[self.inner.len..][0..s.len], s); + self.inner.len = needed_len; + } + }; + + var write_impl: WriteImpl = .{ .inner = &write.inner }; + write_impl.writeStr("Hello World"); + write_impl.flush(); + + try std.testing.expectEqual(write.array_list.items.ptr, write.inner.buf); + try std.testing.expectEqual(write.array_list.items.len, write.inner.len); + try std.testing.expectEqual(write.array_list.capacity, write.inner.cap); + try std.testing.expectEqual(false, write.inner.grow_failed); + try std.testing.expectEqualSlices(u8, "Hello World", write.array_list.items); + + const slice = try write.toOwnedSlice(); + defer gpa.free(slice); + try std.testing.expectEqualSlices(u8, "Hello World", slice); + try std.testing.expectEqualSlices(u8, &.{}, write.array_list.items); +} + +/// Converts a "result" value to its "success" type, or returns `null` if the value is an error. +/// This is `inline` to prevent binary bloat, because each instantiation is expected to be called +/// only once. +pub inline fn success(result: anytype) ?Success(@TypeOf(result)) { + if (!result.is_ok) return null; + if (Success(@TypeOf(result)) == void) return; + return result.unnamed_0.ok; +} + +/// Given the C API representation of a `Result`, returns the type 'T'. +pub fn Success(comptime Result: type) type { + const Union = @FieldType(Result, "unnamed_0"); + if (!@hasField(Union, "ok")) return void; + return @FieldType(Union, "ok"); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8c41733 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +extern crate temporal_capi; diff --git a/src/main.zig b/src/main.zig index f4e0dcb..7ecb9a2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -13,6 +13,4 @@ pub fn main() !void { const dur = try Temporal.Duration.from("P1Y2M3DT4H5M6S"); defer dur.deinit(); - // const dur_str = try dur.toString(allocator, .{}); - // std.debug.print("Duration String: {s}\n", .{dur_str}); } diff --git a/src/root.zig b/src/root.zig index f8a08c6..cffb64a 100644 --- a/src/root.zig +++ b/src/root.zig @@ -113,6 +113,7 @@ test "Temporal.Instant" { "ToStringOptions", "Unit", "RoundingMode", + "Sign", }; try assertDecls(Instant, checks); diff --git a/src/unwind_stubs.zig b/src/unwind_stubs.zig new file mode 100644 index 0000000..12892aa --- /dev/null +++ b/src/unwind_stubs.zig @@ -0,0 +1,29 @@ +//! Provides unwind symbols because apparently Rust still might include these when using panic=abort. +//! +//! See: +//! - https://github.com/rust-lang/rust/issues/47493 +//! - https://code.videolan.org/videolan/vlc/-/merge_requests/2878 + +export fn _Unwind_Backtrace() void {} +export fn _Unwind_DeleteException() void {} +export fn _Unwind_GetDataRelBase() void {} +export fn _Unwind_GetIP() void {} +export fn _Unwind_GetIPInfo() void {} +export fn _Unwind_GetLanguageSpecificData() void {} +export fn _Unwind_GetRegionStart() void {} +export fn _Unwind_GetTextRelBase() void {} +export fn _Unwind_RaiseException() void {} +export fn _Unwind_Resume() void {} +export fn _Unwind_SetGR() void {} +export fn _Unwind_SetIP() void {} +// These appear when building for riscv64 +export fn _Unwind_FindEnclosingFunction() void {} +export fn _Unwind_GetCFA() void {} +// These appear when building for arm +export fn _Unwind_VRS_Get() void {} +export fn _Unwind_VRS_Set() void {} +export fn __gnu_unwind_frame() void {} +// This appears when building for s390x +export fn _Unwind_GetGR() void {} +// This appears when building for x86_64-windows +export fn _GCC_specific_handler() void {}