diff --git a/Cargo.lock b/Cargo.lock index 10ff4f2..5e48fe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,16 @@ 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" @@ -172,6 +182,7 @@ name = "serde_duper" version = "0.1.0" dependencies = [ "duper", + "serde", "serde_core", ] diff --git a/duper/src/ast.rs b/duper/src/ast.rs index b09147c..d9e88e4 100644 --- a/duper/src/ast.rs +++ b/duper/src/ast.rs @@ -12,6 +12,7 @@ pub struct DuperValue<'a> { pub enum DuperInner<'a> { Object(Vec<(Cow<'a, str>, DuperValue<'a>)>), Array(Vec>), + Tuple(Vec>), String(Cow<'a, str>), Bytes(Cow<'a, [u8]>), Integer(i64), @@ -25,6 +26,7 @@ impl<'a> DuperValue<'a> { match self.inner { DuperInner::Object(object) => visitor.visit_object(self.identifier, object), DuperInner::Array(array) => visitor.visit_array(self.identifier, array), + DuperInner::Tuple(tuple) => visitor.visit_tuple(self.identifier, tuple), DuperInner::String(string) => visitor.visit_string(self.identifier, string), DuperInner::Bytes(bytes) => visitor.visit_bytes(self.identifier, bytes), DuperInner::Integer(integer) => visitor.visit_integer(self.identifier, integer), diff --git a/duper/src/builder.rs b/duper/src/builder.rs index 079c190..628f9a2 100644 --- a/duper/src/builder.rs +++ b/duper/src/builder.rs @@ -72,7 +72,7 @@ impl DuperBuilder { } fn build_array(pair: Pair<'_, Rule>) -> Result>, Error> { - debug_assert!(matches!(pair.as_rule(), Rule::array)); + debug_assert!(matches!(pair.as_rule(), Rule::array | Rule::tuple)); pair.into_inner() .map(|pair| Self::build_value(pair)) .collect() @@ -94,6 +94,7 @@ impl DuperBuilder { inner: match next.as_rule() { Rule::object => DuperInner::Object(Self::build_object(next)?), Rule::array => DuperInner::Array(Self::build_array(next)?), + Rule::tuple => DuperInner::Tuple(Self::build_array(next)?), Rule::string => DuperInner::String(Self::unescape_str( next.into_inner().next().unwrap().as_str(), )), diff --git a/duper/src/grammar.pest b/duper/src/grammar.pest index 07c0840..d7781f4 100644 --- a/duper/src/grammar.pest +++ b/duper/src/grammar.pest @@ -8,7 +8,7 @@ duper_trunk = { identifier ~ "(" ~ (object | array) ~ ")" | object | array } identifier = @{ "X-" ~ LETTER ~ ("-"? ~ LETTER | NUMBER)* | UPPERCASE_LETTER ~ (LETTER | NUMBER)* } described_value = { identifier ~ "(" ~ value ~ ")" | value } -value = _{ object | array | string | raw_string | bytes | raw_bytes | float | integer | boolean | null } +value = _{ object | array | tuple | string | raw_string | bytes | raw_bytes | float | integer | boolean | null } object = { "{" ~ ","? ~ "}" | @@ -18,6 +18,11 @@ pair = { key ~ ":" ~ described_value } key = _{ string | raw_string | plain_key } plain_key = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } +tuple = { + "(" ~ "," ~ ")" | + "(" ~ described_value ~ ("," ~ described_value)* ~ ","? ~ ")" +} + array = { "[" ~ ","? ~ "]" | "[" ~ described_value ~ ("," ~ described_value)* ~ ","? ~ "]" diff --git a/duper/src/lib.rs b/duper/src/lib.rs index 67fca5d..0e8669c 100644 --- a/duper/src/lib.rs +++ b/duper/src/lib.rs @@ -9,6 +9,8 @@ mod visitor; pub use ast::{DuperInner, DuperValue}; pub use parser::DuperParser; pub use serializer::DuperSerializer; +pub use types::DuperTypes; +pub use visitor::DuperVisitor; #[cfg(test)] mod tests { diff --git a/duper/src/serializer.rs b/duper/src/serializer.rs index 23bdc3f..66bb26d 100644 --- a/duper/src/serializer.rs +++ b/duper/src/serializer.rs @@ -91,6 +91,44 @@ impl DuperVisitor for DuperSerializer { string } + fn visit_tuple<'a>( + &mut self, + identifier: Option>, + tuple: Vec>, + ) -> Self::Value { + let mut string = String::new(); + let len = tuple.len(); + + if let Some(identifier) = identifier { + string.push_str(identifier.as_ref()); + string.push_str("(("); + for (i, value) in tuple.into_iter().enumerate() { + string.push_str(&value.accept(self)); + if i < len - 1 { + string.push_str(", "); + } + } + if len <= 1 { + string.push(','); + } + string.push_str("))"); + } else { + string.push('('); + for (i, value) in tuple.into_iter().enumerate() { + string.push_str(&value.accept(self)); + if i < len - 1 { + string.push_str(", "); + } + } + if len <= 1 { + string.push(','); + } + string.push(')'); + } + + string + } + fn visit_string<'a>( &mut self, identifier: Option>, diff --git a/duper/src/visitor.rs b/duper/src/visitor.rs index 65a87e3..4a42746 100644 --- a/duper/src/visitor.rs +++ b/duper/src/visitor.rs @@ -10,24 +10,37 @@ pub trait DuperVisitor { identifier: Option>, object: Vec<(Cow<'a, str>, DuperValue<'a>)>, ) -> Self::Value; + fn visit_array<'a>( &mut self, identifier: Option>, array: Vec>, ) -> Self::Value; + + fn visit_tuple<'a>( + &mut self, + identifier: Option>, + tuple: Vec>, + ) -> Self::Value; + fn visit_string<'a>( &mut self, identifier: Option>, string: Cow<'a, str>, ) -> Self::Value; + fn visit_bytes<'a>( &mut self, identifier: Option>, bytes: Cow<'a, [u8]>, ) -> Self::Value; + fn visit_integer<'a>(&mut self, identifier: Option>, integer: i64) -> Self::Value; + fn visit_float<'a>(&mut self, identifier: Option>, float: f64) -> Self::Value; + fn visit_boolean<'a>(&mut self, identifier: Option>, boolean: bool) -> Self::Value; + fn visit_null<'a>(&mut self, identifier: Option>) -> Self::Value; } diff --git a/serde_duper/Cargo.toml b/serde_duper/Cargo.toml index 8ed26bb..eee4c0b 100644 --- a/serde_duper/Cargo.toml +++ b/serde_duper/Cargo.toml @@ -6,3 +6,6 @@ edition = "2024" [dependencies] serde_core = "1.0.228" duper = { path = "../duper" } + +[dev-dependencies] +serde = { version = "1.0.228", features = ["derive"] } diff --git a/serde_duper/src/deserializer.rs b/serde_duper/src/deserializer.rs index a0e8bb5..6b5e0ac 100644 --- a/serde_duper/src/deserializer.rs +++ b/serde_duper/src/deserializer.rs @@ -63,6 +63,13 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let seq = SequenceDeserializer::new(array); visitor.visit_seq(seq) } + Some(DuperValue { + inner: DuperInner::Tuple(tuple), + .. + }) => { + let seq = SequenceDeserializer::new(tuple); + visitor.visit_seq(seq) + } Some(DuperValue { inner: DuperInner::String(string), .. diff --git a/serde_duper/src/lib.rs b/serde_duper/src/lib.rs index 4da6974..7e2233e 100644 --- a/serde_duper/src/lib.rs +++ b/serde_duper/src/lib.rs @@ -3,5 +3,6 @@ mod error; mod serializer; pub use deserializer::{Deserializer, from_str, from_value}; +pub use duper::{DuperInner, DuperValue}; pub use error::Error; pub use serializer::{to_duper, to_string}; diff --git a/serde_duper/src/serializer.rs b/serde_duper/src/serializer.rs index 71e996f..d9a1e0d 100644 --- a/serde_duper/src/serializer.rs +++ b/serde_duper/src/serializer.rs @@ -386,7 +386,7 @@ impl<'a, 'b> ser::SerializeTuple for SerializeTuple<'a, 'b> { fn end(self) -> Result { Ok(DuperValue { identifier: None, - inner: DuperInner::Array(self.elements), + inner: DuperInner::Tuple(self.elements), }) } } @@ -407,7 +407,7 @@ impl<'a, 'b> ser::SerializeTupleStruct for SerializeTupleStruct<'a, 'b> { fn end(self) -> Result { Ok(DuperValue { identifier: Some(Cow::Owned(format!("X-{}", self.name))), - inner: DuperInner::Array(self.elements), + inner: DuperInner::Tuple(self.elements), }) } } @@ -431,7 +431,7 @@ impl<'a, 'b> ser::SerializeTupleVariant for SerializeTupleVariant<'a, 'b> { Cow::Borrowed(self.variant), DuperValue { identifier: None, - inner: DuperInner::Array(self.elements), + inner: DuperInner::Tuple(self.elements), }, )); diff --git a/serde_duper/tests/struct.rs b/serde_duper/tests/struct.rs new file mode 100644 index 0000000..0cebbf1 --- /dev/null +++ b/serde_duper/tests/struct.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +struct Test { + int: usize, + string: String, + bools: Vec, + map: HashMap, +} + +#[test] +fn serialize() { + let value: Test = serde_duper::from_str( + r##" + { + int: 42, + "string": r#"Hello world!"#, + bools: [true, true, false,], + map: { + r#"quantum"#: X-Measurement((-7, null)), + }, + } + "##, + ) + .unwrap(); + assert_eq!(value.int, 42); + assert_eq!(value.string, "Hello world!"); + assert_eq!(value.bools, vec![true, true, false]); + assert_eq!(value.map.len(), 1); + assert_eq!(value.map["quantum"], (-7, ())); + serde_duper::to_string(&value).unwrap(); +}