diff --git a/Cargo.lock b/Cargo.lock index 8e1adae2..81c109e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,121 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + [[package]] name = "bumpalo" version = "3.16.0" @@ -54,6 +163,62 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "combine" version = "4.6.7" @@ -79,6 +244,68 @@ dependencies = [ "libm", ] +[[package]] +name = "diplomat" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0c3a613b09df83bdd42393cd532830a795053102f87461fca1726999829c60" +dependencies = [ + "diplomat_core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diplomat-gen" +version = "0.0.4" +dependencies = [ + "diplomat-tool", +] + +[[package]] +name = "diplomat-runtime" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc1708f176e12755d6d6571ad9b0ebbd3b428223b5cdf63a38eecf1479c13e70" + +[[package]] +name = "diplomat-tool" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398313c0138ba1cd8ffc514f16b356ed17ed924c4efcfe3bc726ed631f6642bf" +dependencies = [ + "askama", + "clap", + "colored", + "diplomat_core", + "displaydoc", + "heck 0.4.1", + "indenter", + "pulldown-cmark", + "quote", + "serde", + "syn", + "syn-inline-mod", + "toml", +] + +[[package]] +name = "diplomat_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e5ba87fee6b8b9dcc575cfbc84ae97b8b9f891fa27f670996a4684e20bd178" +dependencies = [ + "displaydoc", + "either", + "proc-macro2", + "quote", + "serde", + "smallvec", + "strck", + "syn", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -90,6 +317,42 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -229,6 +492,18 @@ dependencies = [ "syn", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "ixdtf" version = "0.3.0" @@ -253,6 +528,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.161" @@ -283,6 +564,38 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -298,6 +611,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "potential_utf" version = "0.1.0" @@ -317,6 +636,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "quote" version = "1.0.35" @@ -352,12 +690,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strck" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.48" @@ -369,6 +728,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-inline-mod" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa6dca1fdb7b2ed46dd534a326725419d4fb10f23d8c85a8b2860e5eb25d0f9" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -380,6 +749,16 @@ dependencies = [ "syn", ] +[[package]] +name = "temporal_capi" +version = "0.0.4" +dependencies = [ + "diplomat", + "diplomat-runtime", + "icu_calendar", + "temporal_rs", +] + [[package]] name = "temporal_rs" version = "0.0.4" @@ -407,6 +786,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tzif" version = "0.3.0" @@ -416,18 +804,36 @@ dependencies = [ "combine", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -502,6 +908,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 803408ce..c85a7b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] resolver = "2" +members = ["diplomat-gen", "temporal_capi"] [workspace.package] edition = "2021" diff --git a/diplomat-gen/Cargo.toml b/diplomat-gen/Cargo.toml new file mode 100644 index 00000000..e72d75c5 --- /dev/null +++ b/diplomat-gen/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "diplomat-gen" +edition.workspace = true +version.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +exclude.workspace = true + +[dependencies] +diplomat-tool = "0.9.0" diff --git a/diplomat-gen/src/main.rs b/diplomat-gen/src/main.rs new file mode 100644 index 00000000..5ce001e3 --- /dev/null +++ b/diplomat-gen/src/main.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +fn main() -> std::io::Result<()> { + let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); + + let capi = manifest.parent().unwrap().join("temporal_capi"); + + let library_config = Default::default(); + + diplomat_tool::gen( + &capi.join("src/lib.rs"), + "cpp", + &{ + let include = capi.join("bindings").join("cpp"); + std::fs::remove_dir_all(&include)?; + std::fs::create_dir(&include)?; + include + }, + &Default::default(), + library_config, + false, + ) +} diff --git a/temporal_capi/Cargo.toml b/temporal_capi/Cargo.toml new file mode 100644 index 00000000..3c013bc8 --- /dev/null +++ b/temporal_capi/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "temporal_capi" +edition.workspace = true +version.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +exclude.workspace = true + +[dependencies] +diplomat = "0.9.0" +diplomat-runtime = "0.9.0" +temporal_rs = { version = "0.0.4", path = ".." } +icu_calendar = { version = "2.0.0-beta1", default-features = false} diff --git a/temporal_capi/bindings/cpp/diplomat_runtime.hpp b/temporal_capi/bindings/cpp/diplomat_runtime.hpp new file mode 100644 index 00000000..21af8be4 --- /dev/null +++ b/temporal_capi/bindings/cpp/diplomat_runtime.hpp @@ -0,0 +1,215 @@ +#ifndef DIPLOMAT_RUNTIME_CPP_H +#define DIPLOMAT_RUNTIME_CPP_H + +#include +#include +#include +#include + +#if __cplusplus >= 202002L +#include +#else +#include +#endif + +namespace diplomat { + +namespace capi { +extern "C" { + +static_assert(sizeof(char) == sizeof(uint8_t), "your architecture's `char` is not 8 bits"); +static_assert(sizeof(char16_t) == sizeof(uint16_t), "your architecture's `char16_t` is not 16 bits"); +static_assert(sizeof(char32_t) == sizeof(uint32_t), "your architecture's `char32_t` is not 32 bits"); + +typedef struct DiplomatWrite { + void* context; + char* buf; + size_t len; + size_t cap; + bool grow_failed; + void (*flush)(struct DiplomatWrite*); + bool (*grow)(struct DiplomatWrite*, size_t); +} DiplomatWrite; + +bool diplomat_is_str(const char* buf, size_t len); + +#define MAKE_SLICES(name, c_ty) \ + typedef struct Diplomat##name##View { \ + const c_ty* data; \ + size_t len; \ + } Diplomat##name##View; \ + typedef struct Diplomat##name##ViewMut { \ + c_ty* data; \ + size_t len; \ + } Diplomat##name##ViewMut; \ + typedef struct Diplomat##name##Array { \ + const c_ty* data; \ + size_t len; \ + } Diplomat##name##Array; + +#define MAKE_SLICES_AND_OPTIONS(name, c_ty) \ + MAKE_SLICES(name, c_ty) \ + typedef struct Option##name {union { c_ty ok; }; bool is_ok; } Option##name; + +MAKE_SLICES_AND_OPTIONS(I8, int8_t) +MAKE_SLICES_AND_OPTIONS(U8, uint8_t) +MAKE_SLICES_AND_OPTIONS(I16, int16_t) +MAKE_SLICES_AND_OPTIONS(U16, uint16_t) +MAKE_SLICES_AND_OPTIONS(I32, int32_t) +MAKE_SLICES_AND_OPTIONS(U32, uint32_t) +MAKE_SLICES_AND_OPTIONS(I64, int64_t) +MAKE_SLICES_AND_OPTIONS(U64, uint64_t) +MAKE_SLICES_AND_OPTIONS(Isize, intptr_t) +MAKE_SLICES_AND_OPTIONS(Usize, size_t) +MAKE_SLICES_AND_OPTIONS(F32, float) +MAKE_SLICES_AND_OPTIONS(F64, double) +MAKE_SLICES_AND_OPTIONS(Bool, bool) +MAKE_SLICES_AND_OPTIONS(Char, char32_t) +MAKE_SLICES(String, char) +MAKE_SLICES(String16, char16_t) +MAKE_SLICES(Strings, DiplomatStringView) +MAKE_SLICES(Strings16, DiplomatString16View) + +} // extern "C" +} // namespace capi + +extern "C" inline void _flush(capi::DiplomatWrite* w) { + std::string* string = reinterpret_cast(w->context); + string->resize(w->len); +}; + +extern "C" inline bool _grow(capi::DiplomatWrite* w, uintptr_t requested) { + std::string* string = reinterpret_cast(w->context); + string->resize(requested); + w->cap = string->length(); + w->buf = &(*string)[0]; + return true; +}; + +inline capi::DiplomatWrite WriteFromString(std::string& string) { + capi::DiplomatWrite w; + w.context = &string; + w.buf = &string[0]; + w.len = string.length(); + w.cap = string.length(); + // Will never become true, as _grow is infallible. + w.grow_failed = false; + w.flush = _flush; + w.grow = _grow; + return w; +}; + +template struct Ok { + T inner; + Ok(T&& i): inner(std::move(i)) {} + // We don't want to expose an lvalue-capable constructor in general + // however there is no problem doing this for trivially copyable types + template::value>::type> + Ok(T i): inner(i) {} + Ok() = default; + Ok(Ok&&) noexcept = default; + Ok(const Ok &) = default; + Ok& operator=(const Ok&) = default; + Ok& operator=(Ok&&) noexcept = default; +}; + +template struct Err { + T inner; + Err(T&& i): inner(std::move(i)) {} + // We don't want to expose an lvalue-capable constructor in general + // however there is no problem doing this for trivially copyable types + template::value>::type> + Err(T i): inner(i) {} + Err() = default; + Err(Err&&) noexcept = default; + Err(const Err &) = default; + Err& operator=(const Err&) = default; + Err& operator=(Err&&) noexcept = default; +}; + +template +class result { +private: + std::variant, Err> val; +public: + result(Ok&& v): val(std::move(v)) {} + result(Err&& v): val(std::move(v)) {} + result() = default; + result(const result &) = default; + result& operator=(const result&) = default; + result& operator=(result&&) noexcept = default; + result(result &&) noexcept = default; + ~result() = default; + bool is_ok() const { + return std::holds_alternative>(this->val); + }; + bool is_err() const { + return std::holds_alternative>(this->val); + }; + + std::optional ok() && { + if (!this->is_ok()) { + return std::nullopt; + } + return std::make_optional(std::move(std::get>(std::move(this->val)).inner)); + }; + std::optional err() && { + if (!this->is_err()) { + return std::nullopt; + } + return std::make_optional(std::move(std::get>(std::move(this->val)).inner)); + } + + void set_ok(T&& t) { + this->val = Ok(std::move(t)); + } + + void set_err(E&& e) { + this->val = Err(std::move(e)); + } + + template + result replace_ok(T2&& t) { + if (this->is_err()) { + return result(Err(std::get>(std::move(this->val)))); + } else { + return result(Ok(std::move(t))); + } + } +}; + +class Utf8Error {}; + +// Use custom std::span on C++17, otherwise use std::span +#if __cplusplus >= 202002L + +template using span = std::span; + +#else // __cplusplus < 202002L + +// C++-17-compatible std::span +template +class span { + +public: + constexpr span(T* data, size_t size) + : data_(data), size_(size) {} + template + constexpr span(std::array::type, N>& arr) + : data_(const_cast(arr.data())), size_(N) {} + constexpr T* data() const noexcept { + return this->data_; + } + constexpr size_t size() const noexcept { + return this->size_; + } +private: + T* data_; + size_t size_; +}; + +#endif // __cplusplus >= 202002L + +} // namespace diplomat + +#endif diff --git a/temporal_capi/src/calendar.rs b/temporal_capi/src/calendar.rs new file mode 100644 index 00000000..86c9f3bb --- /dev/null +++ b/temporal_capi/src/calendar.rs @@ -0,0 +1,61 @@ +#[diplomat::bridge] +#[diplomat::abi_rename = "temporal_rs_{0}"] +#[diplomat::attr(auto, namespace = "temporal_rs")] +pub mod ffi { + use crate::error::ffi::TemporalError; + use diplomat_runtime::DiplomatStr; + + #[diplomat::enum_convert(icu_calendar::any_calendar::AnyCalendarKind, needs_wildcard)] + pub enum AnyCalendarKind { + Buddhist, + Chinese, + Coptic, + Dangi, + Ethiopian, + EthiopianAmeteAlem, + Gregorian, + Hebrew, + Indian, + IslamicCivil, + IslamicObservational, + IslamicTabular, + IslamicUmmAlQura, + Iso, + Japanese, + JapaneseExtended, + Persian, + Roc, + } + + impl AnyCalendarKind { + pub fn get_for_bcp47_string(s: &DiplomatStr) -> Option { + icu_calendar::any_calendar::AnyCalendarKind::get_for_bcp47_bytes(s).map(Into::into) + } + } + + #[diplomat::opaque] + #[diplomat::transparent_convert] + pub struct Calendar(pub temporal_rs::Calendar); + + impl Calendar { + pub fn create(kind: AnyCalendarKind) -> Box { + Box::new(Calendar(temporal_rs::Calendar::new(kind.into()))) + } + + pub fn from_utf8(s: &DiplomatStr) -> Result, TemporalError> { + temporal_rs::Calendar::from_utf8(s) + .map(|c| Box::new(Calendar(c))) + .map_err(Into::into) + } + + pub fn is_iso(&self) -> bool { + self.0.is_iso() + } + + pub fn identifier(&self) -> &'static str { + self.0.identifier() + } + + // TODO the rest of calendar (needs all the date/time types) + } +} diff --git a/temporal_capi/src/error.rs b/temporal_capi/src/error.rs new file mode 100644 index 00000000..a867c21e --- /dev/null +++ b/temporal_capi/src/error.rs @@ -0,0 +1,36 @@ +#[diplomat::bridge] +#[diplomat::abi_rename = "temporal_rs_{0}"] +#[diplomat::attr(auto, namespace = "temporal_rs")] +pub mod ffi { + + #[diplomat::enum_convert(temporal_rs::error::ErrorKind)] + pub enum ErrorKind { + Generic, + Type, + Range, + Syntax, + Assert, + } + + // In the future we might turn this into an opaque type with a msg() field + pub struct TemporalError { + pub kind: ErrorKind, + } + + impl TemporalError { + // internal + pub(crate) fn syntax() -> Self { + TemporalError { + kind: ErrorKind::Syntax, + } + } + } +} + +impl From for ffi::TemporalError { + fn from(other: temporal_rs::TemporalError) -> Self { + Self { + kind: other.kind().into(), + } + } +} diff --git a/temporal_capi/src/lib.rs b/temporal_capi/src/lib.rs new file mode 100644 index 00000000..1a461d52 --- /dev/null +++ b/temporal_capi/src/lib.rs @@ -0,0 +1,8 @@ +#![allow(unused)] // Until we add all the APIs +#![allow(clippy::needless_lifetimes)] // Diplomat requires explicit lifetimes at times + +mod calendar; +mod error; +mod options; + +mod plain_date; diff --git a/temporal_capi/src/options.rs b/temporal_capi/src/options.rs new file mode 100644 index 00000000..e7292d73 --- /dev/null +++ b/temporal_capi/src/options.rs @@ -0,0 +1,91 @@ +#[diplomat::bridge] +#[diplomat::abi_rename = "temporal_rs_{0}"] +#[diplomat::attr(auto, namespace = "temporal_rs")] +pub mod ffi { + use temporal_rs::options; + + #[diplomat::enum_convert(options::ArithmeticOverflow)] + pub enum ArithmeticOverflow { + Constrain, + Reject, + } + #[diplomat::enum_convert(options::Disambiguation)] + pub enum Disambiguation { + Compatible, + Earlier, + Later, + Reject, + } + + #[diplomat::enum_convert(options::DisplayCalendar)] + pub enum DisplayCalendar { + Auto, + Always, + Never, + Critical, + } + + #[diplomat::enum_convert(options::DisplayOffset)] + pub enum DisplayOffset { + Auto, + Never, + } + + #[diplomat::enum_convert(options::DisplayTimeZone)] + pub enum DisplayTimeZone { + Auto, + Never, + Critical, + } + + #[diplomat::enum_convert(options::DurationOverflow)] + pub enum DurationOverflow { + Constrain, + Balance, + } + + #[diplomat::enum_convert(options::OffsetDisambiguation)] + pub enum OffsetDisambiguation { + Use, + Prefer, + Ignore, + Reject, + } + + #[diplomat::enum_convert(options::TemporalRoundingMode)] + pub enum TemporalRoundingMode { + Ceil, + Floor, + Expand, + Trunc, + HalfCeil, + HalfFloor, + HalfExpand, + HalfTrunc, + HalfEven, + } + + #[diplomat::enum_convert(options::TemporalUnit)] + pub enum TemporalUnit { + Auto = 0, + Nanosecond = 1, + Microsecond = 2, + Millisecond = 3, + Second = 4, + Minute = 5, + Hour = 6, + Day = 7, + Week = 8, + Month = 9, + Year = 10, + } + + #[diplomat::enum_convert(options::TemporalUnsignedRoundingMode)] + pub enum TemporalUnsignedRoundingMode { + Infinity, + Zero, + HalfInfinity, + HalfZero, + HalfEven, + } +} diff --git a/temporal_capi/src/plain_date.rs b/temporal_capi/src/plain_date.rs new file mode 100644 index 00000000..428ab5c4 --- /dev/null +++ b/temporal_capi/src/plain_date.rs @@ -0,0 +1,219 @@ +use crate::error::ffi::TemporalError; + +#[diplomat::bridge] +#[diplomat::abi_rename = "temporal_rs_{0}"] +#[diplomat::attr(auto, namespace = "temporal_rs")] +pub mod ffi { + use crate::calendar::ffi::Calendar; + use crate::error::ffi::TemporalError; + use crate::options::ffi::{ArithmeticOverflow, DisplayCalendar}; + use diplomat_runtime::{DiplomatOption, DiplomatStrSlice, DiplomatWrite}; + use std::fmt::Write; + + #[diplomat::opaque] + pub struct PlainDate(pub(crate) temporal_rs::PlainDate); + + pub struct PartialDate<'a> { + pub year: DiplomatOption, + pub month: DiplomatOption, + // None if empty + pub month_code: DiplomatStrSlice<'a>, + pub day: DiplomatOption, + // None if empty + pub era: DiplomatStrSlice<'a>, + pub era_year: DiplomatOption, + pub calendar: &'a Calendar, + } + + impl PlainDate { + pub fn create( + year: i32, + month: u8, + day: u8, + calendar: &Calendar, + ) -> Result, TemporalError> { + temporal_rs::PlainDate::new(year, month, day, calendar.0.clone()) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + pub fn try_create( + year: i32, + month: u8, + day: u8, + calendar: &Calendar, + ) -> Result, TemporalError> { + temporal_rs::PlainDate::try_new(year, month, day, calendar.0.clone()) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + pub fn create_with_overflow( + year: i32, + month: u8, + day: u8, + calendar: &Calendar, + overflow: ArithmeticOverflow, + ) -> Result, TemporalError> { + temporal_rs::PlainDate::new_with_overflow( + year, + month, + day, + calendar.0.clone(), + overflow.into(), + ) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + pub fn from_partial( + partial: PartialDate, + overflow: Option, + ) -> Result, TemporalError> { + temporal_rs::PlainDate::from_partial(partial.try_into()?, overflow.map(Into::into)) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + pub fn with( + &self, + partial: PartialDate, + overflow: Option, + ) -> Result, TemporalError> { + self.0 + .with(partial.try_into()?, overflow.map(Into::into)) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + + pub fn with_calendar(&self, calendar: &Calendar) -> Result, TemporalError> { + self.0 + .with_calendar(calendar.0.clone()) + .map(|x| Box::new(PlainDate(x))) + .map_err(Into::into) + } + + pub fn iso_year(&self) -> i32 { + self.0.iso_year() + } + pub fn iso_month(&self) -> u8 { + self.0.iso_month() + } + pub fn iso_day(&self) -> u8 { + self.0.iso_day() + } + + pub fn calendar<'a>(&'a self) -> &'a Calendar { + Calendar::transparent_convert(self.0.calendar()) + } + + pub fn is_valid(&self) -> bool { + self.0.is_valid() + } + + pub fn days_until(&self, other: &Self) -> i32 { + self.0.days_until(&other.0) + } + + // TODO date arithmetic (needs duration types) + + pub fn year(&self) -> Result { + self.0.year().map_err(Into::into) + } + pub fn month(&self) -> Result { + self.0.month().map_err(Into::into) + } + pub fn month_code(&self, write: &mut DiplomatWrite) -> Result<(), TemporalError> { + let code = self.0.month_code().map_err(Into::::into)?; + // throw away the error, this should always succeed + let _ = write.write_str(&code); + Ok(()) + } + pub fn day(&self) -> Result { + self.0.day().map_err(Into::into) + } + pub fn day_of_week(&self) -> Result { + self.0.day_of_week().map_err(Into::into) + } + pub fn day_of_year(&self) -> Result { + self.0.day_of_year().map_err(Into::into) + } + pub fn week_of_year(&self) -> Result, TemporalError> { + self.0.week_of_year().map_err(Into::into) + } + pub fn year_of_week(&self) -> Result, TemporalError> { + self.0.year_of_week().map_err(Into::into) + } + pub fn days_in_week(&self) -> Result { + self.0.days_in_week().map_err(Into::into) + } + pub fn days_in_month(&self) -> Result { + self.0.days_in_month().map_err(Into::into) + } + pub fn days_in_year(&self) -> Result { + self.0.days_in_year().map_err(Into::into) + } + pub fn months_in_year(&self) -> Result { + self.0.months_in_year().map_err(Into::into) + } + pub fn in_leap_year(&self) -> Result { + self.0.in_leap_year().map_err(Into::into) + } + // Writes an empty string for no era + pub fn era(&self, write: &mut DiplomatWrite) -> Result<(), TemporalError> { + let era = self.0.era().map_err(Into::::into)?; + if let Some(era) = era { + // throw away the error, this should always succeed + let _ = write.write_str(&era); + } + Ok(()) + } + + pub fn era_year(&self) -> Result, TemporalError> { + self.0.era_year().map_err(Into::into) + } + + // TODO conversions (needs other date/time types) + + pub fn to_ixdtf_string( + &self, + display_calendar: DisplayCalendar, + write: &mut DiplomatWrite, + ) { + // TODO this double-allocates, an API returning a Writeable or impl Write would be better + let string = self.0.to_ixdtf_string(display_calendar.into()); + // throw away the error, this should always succeed + let _ = write.write_str(&string); + } + } +} + +impl TryFrom> for temporal_rs::partial::PartialDate { + type Error = TemporalError; + fn try_from(other: ffi::PartialDate<'_>) -> Result { + use temporal_rs::TinyAsciiStr; + + let month_code = if other.month_code.is_empty() { + None + } else { + Some( + TinyAsciiStr::try_from_utf8(other.month_code.into()) + .map_err(|_| TemporalError::syntax())?, + ) + }; + + let era = if other.era.is_empty() { + None + } else { + Some( + TinyAsciiStr::try_from_utf8(other.era.into()) + .map_err(|_| TemporalError::syntax())?, + ) + }; + Ok(Self { + year: other.year.into(), + month: other.month.into(), + month_code, + day: other.day.into(), + era_year: other.era_year.into(), + era, + calendar: other.calendar.0.clone(), + }) + } +}