Skip to content

Commit

Permalink
Set up basic diplomat workflow (#163)
Browse files Browse the repository at this point in the history
Progress on #104

This adds a simple diplomat integration, but does not use it yet. When
we start using it we should add CI that runs `cargo run -p diplomat-gen`
and ensures there is no diff.
  • Loading branch information
Manishearth authored Jan 21, 2025
1 parent 3a8f8c1 commit 96e16b1
Show file tree
Hide file tree
Showing 11 changed files with 1,098 additions and 0 deletions.
415 changes: 415 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
resolver = "2"
members = ["diplomat-gen", "temporal_capi"]

[workspace.package]
edition = "2021"
Expand Down
13 changes: 13 additions & 0 deletions diplomat-gen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
23 changes: 23 additions & 0 deletions diplomat-gen/src/main.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
16 changes: 16 additions & 0 deletions temporal_capi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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}
215 changes: 215 additions & 0 deletions temporal_capi/bindings/cpp/diplomat_runtime.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#ifndef DIPLOMAT_RUNTIME_CPP_H
#define DIPLOMAT_RUNTIME_CPP_H

#include <optional>
#include <string>
#include <type_traits>
#include <variant>

#if __cplusplus >= 202002L
#include <span>
#else
#include <array>
#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<std::string*>(w->context);
string->resize(w->len);
};

extern "C" inline bool _grow(capi::DiplomatWrite* w, uintptr_t requested) {
std::string* string = reinterpret_cast<std::string*>(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<class T> 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<typename X = T, typename = typename std::enable_if<std::is_trivially_copyable<X>::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<class T> 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<typename X = T, typename = typename std::enable_if<std::is_trivially_copyable<X>::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 T, class E>
class result {
private:
std::variant<Ok<T>, Err<E>> val;
public:
result(Ok<T>&& v): val(std::move(v)) {}
result(Err<E>&& 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<Ok<T>>(this->val);
};
bool is_err() const {
return std::holds_alternative<Err<E>>(this->val);
};

std::optional<T> ok() && {
if (!this->is_ok()) {
return std::nullopt;
}
return std::make_optional(std::move(std::get<Ok<T>>(std::move(this->val)).inner));
};
std::optional<E> err() && {
if (!this->is_err()) {
return std::nullopt;
}
return std::make_optional(std::move(std::get<Err<E>>(std::move(this->val)).inner));
}

void set_ok(T&& t) {
this->val = Ok<T>(std::move(t));
}

void set_err(E&& e) {
this->val = Err<E>(std::move(e));
}

template<typename T2>
result<T2, E> replace_ok(T2&& t) {
if (this->is_err()) {
return result<T2, E>(Err<E>(std::get<Err<E>>(std::move(this->val))));
} else {
return result<T2, E>(Ok<T2>(std::move(t)));
}
}
};

class Utf8Error {};

// Use custom std::span on C++17, otherwise use std::span
#if __cplusplus >= 202002L

template<class T> using span = std::span<T>;

#else // __cplusplus < 202002L

// C++-17-compatible std::span
template<class T>
class span {

public:
constexpr span(T* data, size_t size)
: data_(data), size_(size) {}
template<size_t N>
constexpr span(std::array<typename std::remove_const<T>::type, N>& arr)
: data_(const_cast<T*>(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
61 changes: 61 additions & 0 deletions temporal_capi/src/calendar.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<Self> {
Box::new(Calendar(temporal_rs::Calendar::new(kind.into())))
}

pub fn from_utf8(s: &DiplomatStr) -> Result<Box<Self>, 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)
}
}
36 changes: 36 additions & 0 deletions temporal_capi/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<temporal_rs::TemporalError> for ffi::TemporalError {
fn from(other: temporal_rs::TemporalError) -> Self {
Self {
kind: other.kind().into(),
}
}
}
8 changes: 8 additions & 0 deletions temporal_capi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 96e16b1

Please sign in to comment.