mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-01 21:40:58 +00:00
Merge pull request #3748 from rtfeldman/serde
Add serde serializers/deserializers to roc_std
This commit is contained in:
commit
07eed2c4a6
6 changed files with 219 additions and 6 deletions
|
|
@ -64,7 +64,7 @@ build-rust-test:
|
|||
RUN apt -y install gcc-10 g++-10 && rm /usr/bin/gcc && ln -s /usr/bin/gcc-10 /usr/bin/gcc # gcc-9 maybe causes segfault
|
||||
RUN gcc --version
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
|
||||
cargo test --locked --release --features with_sound serde --workspace --no-run && sccache --show-stats
|
||||
|
||||
check-typos:
|
||||
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
|
||||
|
|
@ -82,7 +82,7 @@ test-rust:
|
|||
RUN gcc --version
|
||||
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --features with_sound --workspace && sccache --show-stats
|
||||
cargo test --locked --release --features with_sound serde --workspace && sccache --show-stats
|
||||
# test the dev and wasm backend: they require an explicit feature flag.
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
|
|
@ -99,7 +99,7 @@ test-rust:
|
|||
# NOTE: disabled until zig 0.9
|
||||
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
|
||||
# RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
# cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
# cargo test --locked --release --features with_sound serde --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
# make sure website deployment works (that is, make sure build.sh returns status code 0)
|
||||
ENV REPL_DEBUG=1
|
||||
RUN bash www/build.sh
|
||||
|
|
@ -126,7 +126,7 @@ build-nightly-release:
|
|||
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
|
||||
# version.txt is used by the CLI: roc --version
|
||||
RUN ./ci/write_version.sh
|
||||
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release
|
||||
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound serde --release
|
||||
RUN ./ci/package_release.sh roc_linux_x86_64.tar.gz
|
||||
SAVE ARTIFACT ./roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
|
||||
|
||||
|
|
|
|||
33
crates/roc_std/Cargo.lock
generated
33
crates/roc_std/Cargo.lock
generated
|
|
@ -78,6 +78,12 @@ dependencies = [
|
|||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.119"
|
||||
|
|
@ -197,7 +203,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
|||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"indoc",
|
||||
|
|
@ -205,9 +211,34 @@ dependencies = [
|
|||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ version = "0.0.1"
|
|||
[dependencies]
|
||||
static_assertions = "1.1.0"
|
||||
arrayvec = "0.7.2"
|
||||
serde = { version = "1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
libc = "0.2.106"
|
||||
pretty_assertions = "1.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
libc = "0.2.106"
|
||||
serde_json = "1.0.83"
|
||||
|
||||
[features]
|
||||
std = []
|
||||
serde = ["dep:serde"]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,15 @@ use core::{
|
|||
|
||||
use crate::{roc_alloc, roc_dealloc, roc_realloc, storage::Storage};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use core::marker::PhantomData;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{
|
||||
de::{Deserializer, Visitor},
|
||||
ser::{SerializeSeq, Serializer},
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RocList<T> {
|
||||
elements: Option<NonNull<ManuallyDrop<T>>>,
|
||||
|
|
@ -586,3 +595,76 @@ impl<T: Clone> FromIterator<T> for RocList<T> {
|
|||
list
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<T: Serialize> Serialize for RocList<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.len()))?;
|
||||
for item in self {
|
||||
seq.serialize_element(item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, T> Deserialize<'de> for RocList<T>
|
||||
where
|
||||
// TODO: I'm not sure about requiring clone here. Is that fine? Is that
|
||||
// gonna mean lots of extra allocations?
|
||||
T: Deserialize<'de> + core::clone::Clone,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(RocListVisitor::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
struct RocListVisitor<T> {
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<T> RocListVisitor<T> {
|
||||
fn new() -> Self {
|
||||
RocListVisitor {
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, T> Visitor<'de> for RocListVisitor<T>
|
||||
where
|
||||
T: Deserialize<'de> + core::clone::Clone,
|
||||
{
|
||||
type Value = RocList<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(formatter, "a list of strings")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let mut out = match seq.size_hint() {
|
||||
Some(hint) => RocList::with_capacity(hint),
|
||||
None => RocList::empty(),
|
||||
};
|
||||
|
||||
while let Some(next) = seq.next_element()? {
|
||||
// TODO: it would be ideal to call `out.push` here, but we haven't
|
||||
// implemented that yet! I think this is also why we need Clone.
|
||||
out.extend_from_slice(&[next])
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{
|
||||
de::{Deserializer, Visitor},
|
||||
ser::Serializer,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
|
||||
use core::{
|
||||
cmp,
|
||||
convert::TryFrom,
|
||||
|
|
@ -708,3 +715,45 @@ impl Hash for RocStr {
|
|||
self.as_str().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for RocStr {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> Deserialize<'de> for RocStr {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// TODO: using deserialize_string here instead of deserialize_str here
|
||||
// because I think we'd "benefit from taking ownership of buffered data
|
||||
// owned by the Deserializer." is that correct?
|
||||
deserializer.deserialize_string(RocStrVisitor {})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
struct RocStrVisitor {}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> Visitor<'de> for RocStrVisitor {
|
||||
type Value = RocStr;
|
||||
|
||||
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(formatter, "a string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(RocStr::from(value))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,29 @@ mod test_roc_std {
|
|||
assert_eq!(roc_str.capacity(), 5000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn str_short_serde_roundtrip() {
|
||||
let orig = RocStr::from("x");
|
||||
|
||||
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
|
||||
let deserialized = serde_json::from_str(&serialized).expect("failed to deserialize string");
|
||||
|
||||
assert_eq!(orig, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn str_long_serde_roundtrip() {
|
||||
// How about a little philosophy to accompany test failures?
|
||||
let orig = RocStr::from("If there's a remedy when trouble strikes, what reason is there for dejection? And if there is no help for it, what use is there in being glum? -- Shantideva, The Way of the Bodhisattva");
|
||||
|
||||
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
|
||||
let deserialized = serde_json::from_str(&serialized).expect("failed to deserialize string");
|
||||
|
||||
assert_eq!(orig, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_small_list() {
|
||||
let mut roc_list = RocList::<RocStr>::empty();
|
||||
|
|
@ -156,6 +179,31 @@ mod test_roc_std {
|
|||
assert_eq!(roc_list.capacity(), 5000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn short_list_roundtrip() {
|
||||
let items: [u8; 4] = [1, 3, 3, 7];
|
||||
let orig = RocList::from_slice(&items);
|
||||
|
||||
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
|
||||
let deserialized =
|
||||
serde_json::from_str::<RocList<u8>>(&serialized).expect("failed to deserialize string");
|
||||
|
||||
assert_eq!(orig, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn long_list_roundtrip() {
|
||||
let orig = RocList::from_iter(1..100);
|
||||
|
||||
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
|
||||
let deserialized =
|
||||
serde_json::from_str::<RocList<u8>>(&serialized).expect("failed to deserialize string");
|
||||
|
||||
assert_eq!(orig, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_from_iter() {
|
||||
let elems: [i64; 5] = [1, 2, 3, 4, 5];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue