diff --git a/README.md b/README.md index 2e65c854a..5eebbf225 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ RON is a simple readable data serialization format that looks similar to Rust syntax. It's designed to support all of [Serde's data model](https://serde.rs/data-model.html), so -structs, enums, tuples, arrays, generic maps, and primitive values. +structs, enums, tuples, arrays, generic maps, ranges, and primitive values. ## Example @@ -57,6 +57,7 @@ GameConfig( // optional struct name * Lists: `["abc", "def"]` * Structs: `( foo: 1.0, bar: ( baz: "I'm nested" ) )` * Maps: `{ "arbitrary": "keys", "are": "allowed" }` +* Ranges: `3..5`, `-2.1..=7.3`, `..-15`, `..10.5`, `..=13`, `..` > **Note:** Serde's data model represents fixed-size Rust arrays as tuple (instead of as list) diff --git a/docs/grammar.md b/docs/grammar.md index 1d0e6cc5f..3314fab8c 100644 --- a/docs/grammar.md +++ b/docs/grammar.md @@ -40,7 +40,7 @@ For the extension names see the [`extensions.md`][exts] document. ## Value ```ebnf -value = integer | byte | float | string | byte_string | char | bool | option | list | map | tuple | struct | enum_variant; +value = integer | byte | float | string | byte_string | char | bool | option | list | map | tuple | struct | enum_variant | range; ``` ## Numbers @@ -51,6 +51,8 @@ digit_binary = "0" | "1"; digit_octal = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"; digit_hexadecimal = digit | "A" | "a" | "B" | "b" | "C" | "c" | "D" | "d" | "E" | "e" | "F" | "f"; +number = integer | byte | float; + integer = ["+" | "-"], unsigned, [integer_suffix]; integer_suffix = ("i", "u"), ("8", "16", "32", "64", "128"); @@ -139,6 +141,24 @@ option = "None" | option_some; option_some = "Some", ws, "(", ws, value, ws, ")"; ``` +## Range + +```ebnf +range = range_from_to_inclusive | range_from_to_exclusive | range_from | range_to_inclusive | range_to_exclusive | range_full; +range_from_to_inclusive = number, ws, "..=", ws, number; +range_from_to_exclusive = number, ws, "..", ws, number; +range_from = number, ws, ".."; +range_to_inclusive = "..=", ws, number; +range_to_exclusive = "..", ws, number; +range_full = ".."; +``` + +> Note: Alternatives are ordered by specificity — `..=` must be matched before `..` + to avoid ambiguity. A parser should attempt the longer token (`..=`) first. + The six range forms correspond directly to Rust's range types: + `x..y` → `Range`, `x..=y` → `RangeInclusive`, `x..` → `RangeFrom`, + `..y` → `RangeTo`, `..=y` → `RangeToInclusive`, `..` → `RangeFull`. + ## List ```ebnf @@ -188,4 +208,4 @@ ident_raw = "r", "#", ident_raw_rest, { ident_raw_rest }; ident_raw_rest = ident_std_rest | "." | "+" | "-"; ``` -> Note: [XID_Start](http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Start%3A%5D&abb=on&g=&i=) and [XID_Continue](http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Continue%3A%5D&abb=on&g=&i=) refer to Unicode character sets. +> Note: [XID_Start](http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Start%3A%5D&abb=on&g=&i=) and [XID_Continue](http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AXID_Continue%3A%5D&abb=on&g=&i=) refer to Unicode character sets. \ No newline at end of file diff --git a/src/de/mod.rs b/src/de/mod.rs index 61008db56..fe4c83f1c 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -17,6 +17,7 @@ use crate::{ extensions::Extensions, options::Options, parse::{NewtypeMode, ParsedByteStr, ParsedStr, Parser, StructType, TupleMode}, + value::NumberDeserializer, }; #[cfg(feature = "std")] @@ -232,6 +233,31 @@ impl<'de> Deserializer<'de> { } } + fn handle_float_range_or_value( + &mut self, + number: crate::value::Number, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.parser.consume_str("..=") { + let end = self.parser.any_number()?; + return visitor.visit_map(RangeMapAccess::new(number, end, "end")); + } else if self.parser.consume_str("..") { + if self + .parser + .peek_char() + .map_or(true, |c| !self.parser.is_number_start(c)) + { + return visitor.visit_map(RangeFromMapAccess::new(number)); + } + let end = self.parser.any_number()?; + return visitor.visit_map(RangeMapAccess::new(number, end, "end")); + } + number.visit(visitor) + } + /// Called from /// [`deserialize_struct`][serde::Deserializer::deserialize_struct], /// [`struct_variant`][serde::de::VariantAccess::struct_variant], and @@ -329,13 +355,17 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } else if self.parser.consume_str("()") { return visitor.visit_unit(); } else if self.parser.consume_ident("inf") || self.parser.consume_ident("inff32") { - return visitor.visit_f32(core::f32::INFINITY); + let number = crate::value::Number::F32(crate::value::F32(core::f32::INFINITY)); + return self.handle_float_range_or_value(number, visitor); } else if self.parser.consume_ident("inff64") { - return visitor.visit_f64(core::f64::INFINITY); + let number = crate::value::Number::F64(crate::value::F64(core::f64::INFINITY)); + return self.handle_float_range_or_value(number, visitor); } else if self.parser.consume_ident("NaN") || self.parser.consume_ident("NaNf32") { - return visitor.visit_f32(core::f32::NAN); + let number = crate::value::Number::F32(crate::value::F32(core::f32::NAN)); + return self.handle_float_range_or_value(number, visitor); } else if self.parser.consume_ident("NaNf64") { - return visitor.visit_f64(core::f64::NAN); + let number = crate::value::Number::F64(crate::value::F64(core::f64::NAN)); + return self.handle_float_range_or_value(number, visitor); } // `skip_identifier` does not change state if it fails @@ -349,10 +379,34 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { '(' => self.handle_any_struct(visitor, None), '[' => self.deserialize_seq(visitor), '{' => self.deserialize_map(visitor), - '0'..='9' | '+' | '-' | '.' => self.parser.any_number()?.visit(visitor), + '0'..='9' | '+' | '-' | '.' | 'b' => { + if self.parser.check_char('b') && !self.parser.src().starts_with("b'") { + return self.deserialize_byte_buf(visitor); + } + + if self.parser.consume_str("..") { + return visitor.visit_unit(); + } + + let start = self.parser.any_number()?; + + if self.parser.consume_str("..=") { + let end = self.parser.any_number()?; + return visitor.visit_map(RangeMapAccess::new(start, end, "end")); + } else if self.parser.consume_str("..") { + if self.parser.peek_char().map_or(true, |c| { + !matches!(c, '0'..='9' | '+' | '-' | 'b') + || (c == 'b' && !self.parser.src().starts_with("b'")) + }) { + return visitor.visit_map(RangeFromMapAccess::new(start)); + } + let end = self.parser.any_number()?; + return visitor.visit_map(RangeMapAccess::new(start, end, "end")); + } + + start.visit(visitor) + } '"' | 'r' => self.deserialize_string(visitor), - 'b' if self.parser.src().starts_with("b'") => self.parser.any_number()?.visit(visitor), - 'b' => self.deserialize_byte_buf(visitor), '\'' => self.deserialize_char(visitor), other => Err(Error::UnexpectedChar(other)), } @@ -550,6 +604,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + if name == "RangeFull" && self.parser.consume_str("..") { + return visitor.visit_unit(); + } + if self.newtype_variant || self.parser.consume_struct_name(name)? { self.newtype_variant = false; @@ -715,12 +773,93 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { fn deserialize_struct( self, name: &'static str, - _fields: &'static [&'static str], + fields: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { + if name == "Range" || name == "RangeInclusive" { + if let ["start", end_field @ ("end" | "last")] = fields { + if let Some(c) = self.parser.peek_char() { + if self.parser.is_number_start(c) { + let start = self.parser.any_number()?; + + let inclusive = if self.parser.consume_str("..=") { + true + } else if self.parser.consume_str("..") { + false + } else { + return Err(Error::ExpectedRangeSyntax); + }; + + if inclusive && name == "Range" { + return Err(Error::Message(String::from( + "expected `..` for `Range`, found `..=`", + ))); + } + if !inclusive && name == "RangeInclusive" { + return Err(Error::Message(String::from( + "expected `..=` for `RangeInclusive`, found `..`", + ))); + } + + let end = self.parser.any_number()?; + return visitor.visit_map(RangeMapAccess::new(start, end, end_field)); + } + } + } + } + + if fields == ["start"] && name == "RangeFrom" { + if let Some(c) = self.parser.peek_char() { + if self.parser.is_number_start(c) + || self.parser.check_ident("inf") + || self.parser.check_ident("inff32") + || self.parser.check_ident("inff64") + || self.parser.check_ident("NaN") + || self.parser.check_ident("NaNf32") + || self.parser.check_ident("NaNf64") + { + let start = self.parser.any_number()?; + if self.parser.consume_str("..=") { + return Err(Error::Message(String::from( + "expected `..` for `RangeFrom`, found `..=`", + ))); + } else if !self.parser.consume_str("..") { + return Err(Error::ExpectedRangeSyntax); + } + return visitor.visit_map(RangeFromMapAccess::new(start)); + } + } + } + + if fields == ["end"] && name == "RangeTo" { + if self.parser.check_str("..=") || self.parser.check_str("..") { + if self.parser.consume_str("..=") { + return Err(Error::Message(String::from( + "expected `..` for `RangeTo`, found `..=`", + ))); + } + self.parser.consume_str(".."); + let end = self.parser.any_number()?; + return visitor.visit_map(RangeToMapAccess::new(end, "end")); + } + } + + if matches!(fields, ["end"] | ["last"]) && name == "RangeToInclusive" { + if self.parser.check_str("..=") || self.parser.check_str("..") { + if !self.parser.consume_str("..=") { + self.parser.consume_str(".."); + return Err(Error::Message(String::from( + "expected `..=` for `RangeToInclusive`, found `..`", + ))); + } + let end = self.parser.any_number()?; + return visitor.visit_map(RangeToMapAccess::new(end, fields[0])); + } + } + if !self.newtype_variant { self.parser.consume_struct_name(name)?; } @@ -827,6 +966,142 @@ impl<'a, 'de> CommaSeparated<'a, 'de> { } } +enum RangeMapState { + StartKey, + StartValue, + EndKey, + EndValue, + Done, +} + +struct RangeMapAccess { + start: crate::value::Number, + end: crate::value::Number, + state: RangeMapState, + end_key: &'static str, +} + +impl RangeMapAccess { + fn new(start: crate::value::Number, end: crate::value::Number, end_key: &'static str) -> Self { + RangeMapAccess { + start, + end, + state: RangeMapState::StartKey, + end_key, + } + } +} + +impl<'de> de::MapAccess<'de> for RangeMapAccess { + type Error = Error; + + fn next_key_seed>(&mut self, seed: K) -> Result> { + match self.state { + RangeMapState::StartKey => { + self.state = RangeMapState::StartValue; + seed.deserialize(de::value::StrDeserializer::::new("start")) + .map(Some) + } + RangeMapState::EndKey => { + self.state = RangeMapState::EndValue; + seed.deserialize(de::value::StrDeserializer::::new(self.end_key)) + .map(Some) + } + _ => Ok(None), + } + } + + fn next_value_seed>(&mut self, seed: V) -> Result { + match self.state { + RangeMapState::StartValue => { + self.state = RangeMapState::EndKey; + seed.deserialize(NumberDeserializer(self.start)) + } + RangeMapState::EndValue => { + self.state = RangeMapState::Done; + seed.deserialize(NumberDeserializer(self.end)) + } + _ => Err(Error::ExpectedDifferentLength { + expected: String::from("map of length 2"), + found: 3, + }), + } + } +} + +struct RangeFromMapAccess { + start: crate::value::Number, + done: bool, +} + +impl RangeFromMapAccess { + fn new(start: crate::value::Number) -> Self { + RangeFromMapAccess { start, done: false } + } +} + +impl<'de> de::MapAccess<'de> for RangeFromMapAccess { + type Error = Error; + + fn next_key_seed>(&mut self, seed: K) -> Result> { + if self.done { + return Ok(None); + } + seed.deserialize(de::value::StrDeserializer::::new("start")) + .map(Some) + } + + fn next_value_seed>(&mut self, seed: V) -> Result { + if self.done { + return Err(Error::ExpectedDifferentLength { + expected: String::from("map of length 1"), + found: 2, + }); + } + self.done = true; + seed.deserialize(NumberDeserializer(self.start)) + } +} + +struct RangeToMapAccess { + end: crate::value::Number, + end_key: &'static str, + done: bool, +} + +impl RangeToMapAccess { + fn new(end: crate::value::Number, end_key: &'static str) -> Self { + RangeToMapAccess { + end, + end_key, + done: false, + } + } +} + +impl<'de> de::MapAccess<'de> for RangeToMapAccess { + type Error = Error; + + fn next_key_seed>(&mut self, seed: K) -> Result> { + if self.done { + return Ok(None); + } + seed.deserialize(de::value::StrDeserializer::::new(self.end_key)) + .map(Some) + } + + fn next_value_seed>(&mut self, seed: V) -> Result { + if self.done { + return Err(Error::ExpectedDifferentLength { + expected: String::from("map of length 1"), + found: 2, + }); + } + self.done = true; + seed.deserialize(NumberDeserializer(self.end)) + } +} + impl<'de, 'a> de::SeqAccess<'de> for CommaSeparated<'a, 'de> { type Error = Error; diff --git a/src/error.rs b/src/error.rs index b5dc9b208..a4ddc69e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -112,6 +112,7 @@ pub enum Error { ExpectedRawValue, ExceededRecursionLimit, ExpectedStructName(String), + ExpectedRangeSyntax, } impl fmt::Display for SpannedError { @@ -286,6 +287,7 @@ impl fmt::Display for Error { "Expected the explicit struct name {}, but none was found", Identifier(name) ), + Error::ExpectedRangeSyntax => f.write_str("Expected `..` or `..=` for range syntax"), } } } diff --git a/src/parse.rs b/src/parse.rs index 13a876735..bb37ef20e 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -144,6 +144,10 @@ impl<'a> Parser<'a> { } } + pub fn is_number_start(&self, c: char) -> bool { + matches!(c, '0'..='9' | '+' | '-' | '.' | 'b') && (c != 'b' || self.src().starts_with("b'")) + } + pub fn advance_bytes(&mut self, bytes: usize) { self.prev_cursor = self.cursor; self.cursor.cursor += bytes; @@ -461,6 +465,16 @@ impl<'a> Parser<'a> { } pub fn any_number(&mut self) -> Result { + if self.consume_ident("inf") || self.consume_ident("inff32") { + return Ok(Number::F32(crate::value::F32(core::f32::INFINITY).into())); + } else if self.consume_ident("inff64") { + return Ok(Number::F64(crate::value::F64(core::f64::INFINITY).into())); + } else if self.consume_ident("NaN") || self.consume_ident("NaNf32") { + return Ok(Number::F32(crate::value::F32(core::f32::NAN).into())); + } else if self.consume_ident("NaNf64") { + return Ok(Number::F64(crate::value::F64(core::f64::NAN).into())); + } + if self.next_bytes_is_float() { return match self.float::()? { ParsedFloat::F32(v) => Ok(Number::F32(v.into())), @@ -837,7 +851,9 @@ impl<'a> Parser<'a> { } } - let num_bytes = self.next_chars_while_len(is_float_char); + let raw_bytes = self.next_chars_while_len(is_float_char); + let src = &self.src()[..raw_bytes]; + let num_bytes = src.find("..").unwrap_or(raw_bytes); if num_bytes == 0 { return Err(Error::ExpectedFloat); @@ -1019,7 +1035,12 @@ impl<'a> Parser<'a> { '+' | '-' => 1, _ => 0, }; - let valid_float_len = self.next_chars_while_from_len(skip, is_float_char); + let raw_float_len = self.next_chars_while_from_len(skip, is_float_char); + // Trim at ".." to avoid treating range operators as float chars + let valid_float_len = self.src()[skip..] + .find("..") + .map(|i| i.min(raw_float_len)) + .unwrap_or(raw_float_len); let valid_int_len = self.next_chars_while_from_len(skip, is_int_char); valid_float_len > valid_int_len } else { diff --git a/src/ser/mod.rs b/src/ser/mod.rs index d487208cb..e5d2008a4 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -120,6 +120,8 @@ pub struct PrettyConfig { pub number_suffixes: bool, /// Additional path-based field metadata to serialize pub path_meta: Option, + /// Enable compact range syntax, e.g. `0..5` instead of `(start: 0, end: 5)`. + pub compact_ranges: bool, } impl PrettyConfig { @@ -348,6 +350,35 @@ impl PrettyConfig { self } + + /// Configures whether ranges should be serialized using compact syntax (`true`) + /// or as regular structs (`false`). + /// + /// When `false`, `0..5` will serialize to + /// ```ignore + /// ( + /// start: 0, + /// end: 5, + /// ) + /// # ; + /// ``` + /// When `true`, `0..5` will instead serialize to + /// ```ignore + /// 0..5 + /// # ; + /// ``` + /// and `1..=3` will serialize to + /// ```ignore + /// 1..=3 + /// # ; + /// ``` + /// + /// Default: `false` + #[must_use] + pub fn compact_ranges(mut self, compact_ranges: bool) -> Self { + self.compact_ranges = compact_ranges; + self + } } impl Default for PrettyConfig { @@ -371,6 +402,7 @@ impl Default for PrettyConfig { compact_maps: false, number_suffixes: false, path_meta: None, + compact_ranges: false, } } } @@ -488,6 +520,12 @@ impl Serializer { .map_or(false, |(ref config, _)| config.number_suffixes) } + fn compact_ranges(&self) -> bool { + self.pretty + .as_ref() + .map_or(false, |(ref config, _)| config.compact_ranges) + } + fn extensions(&self) -> Extensions { self.default_extensions | self @@ -866,6 +904,11 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { } fn serialize_unit_struct(self, name: &'static str) -> Result<()> { + if self.compact_ranges() && name == "RangeFull" { + self.output.write_str("..")?; + return Ok(()); + } + if self.struct_names() && !self.newtype_variant { self.write_identifier(name)?; @@ -1050,6 +1093,29 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { fn serialize_struct(self, name: &'static str, len: usize) -> Result { let old_newtype_variant = self.newtype_variant; + if self.compact_ranges() + && ((len == 2 && (name == "Range" || name == "RangeInclusive")) + || (len == 1 + && (name == "RangeFrom" || name == "RangeTo" || name == "RangeToInclusive"))) + { + self.newtype_variant = false; + self.implicit_some_depth = 0; + return Ok(Compound::new_range( + self, + if name == "RangeInclusive" { + RangeKind::RangeInclusive + } else if name == "RangeFrom" { + RangeKind::RangeFrom + } else if name == "RangeTo" { + RangeKind::RangeTo + } else if name == "RangeToInclusive" { + RangeKind::RangeToInclusive + } else { + RangeKind::Range + }, + )); + } + self.newtype_variant = false; self.implicit_some_depth = 0; @@ -1106,6 +1172,7 @@ pub struct Compound<'a, W: fmt::Write> { state: State, newtype_variant: bool, sequence_index: usize, + range: Option, } impl<'a, W: fmt::Write> Compound<'a, W> { @@ -1115,10 +1182,246 @@ impl<'a, W: fmt::Write> Compound<'a, W> { state: State::First, newtype_variant, sequence_index: 0, + range: None, + } + } + + fn new_range(ser: &'a mut Serializer, kind: RangeKind) -> Self { + Compound { + ser, + state: State::First, + newtype_variant: false, + sequence_index: 0, + range: Some(RangeCompound::new(kind)), } } } +enum RangeKind { + Range, + RangeInclusive, + RangeFrom, + RangeTo, + RangeToInclusive, +} + +struct RangeCompound { + kind: RangeKind, + first: Option, + second: Option, + fallback: bool, +} + +impl RangeCompound { + fn new(kind: RangeKind) -> Self { + RangeCompound { + kind, + first: None, + second: None, + fallback: false, + } + } + + /// Try to serialize `value` as a number into a String buffer. + /// Returns `Some(s)` if numeric, `None` otherwise. + fn try_serialize_number(value: &T) -> Option { + struct NumberSerializer; + + impl ser::Serializer for NumberSerializer { + type Ok = crate::value::Number; + type Error = Error; + type SerializeSeq = ser::Impossible; + type SerializeTuple = ser::Impossible; + type SerializeTupleStruct = ser::Impossible; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = ser::Impossible; + type SerializeStruct = ser::Impossible; + type SerializeStructVariant = ser::Impossible; + + fn serialize_i8(self, v: i8) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_i16(self, v: i16) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_i32(self, v: i32) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_i64(self, v: i64) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_u8(self, v: u8) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_u16(self, v: u16) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_u32(self, v: u32) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_u64(self, v: u64) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_f32(self, v: f32) -> Result { + Ok(crate::value::Number::new(v)) + } + fn serialize_f64(self, v: f64) -> Result { + Ok(crate::value::Number::new(v)) + } + #[cfg(feature = "integer128")] + fn serialize_i128(self, v: i128) -> Result { + Ok(crate::value::Number::new(v)) + } + #[cfg(feature = "integer128")] + fn serialize_u128(self, v: u128) -> Result { + Ok(crate::value::Number::new(v)) + } + + fn serialize_bool(self, _: bool) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("bool"), + }) + } + fn serialize_char(self, _: char) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("char"), + }) + } + fn serialize_str(self, _: &str) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("string"), + }) + } + fn serialize_bytes(self, _: &[u8]) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("bytes"), + }) + } + fn serialize_none(self) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("None"), + }) + } + fn serialize_some(self, _: &T) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("Some"), + }) + } + fn serialize_unit(self) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("unit"), + }) + } + fn serialize_unit_struct(self, _: &'static str) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("unit struct"), + }) + } + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("unit variant"), + }) + } + fn serialize_newtype_struct( + self, + _: &'static str, + _: &T, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("newtype struct"), + }) + } + fn serialize_newtype_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: &T, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("newtype variant"), + }) + } + fn serialize_seq(self, _: Option) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("seq"), + }) + } + fn serialize_tuple(self, _: usize) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("tuple"), + }) + } + fn serialize_tuple_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("tuple struct"), + }) + } + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("tuple variant"), + }) + } + fn serialize_map(self, _: Option) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("map"), + }) + } + fn serialize_struct(self, _: &'static str, _: usize) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("struct"), + }) + } + fn serialize_struct_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(Error::InvalidValueForType { + expected: String::from("number"), + found: String::from("struct variant"), + }) + } + } + + value.serialize(NumberSerializer).ok() + } +} + impl<'a, W: fmt::Write> Drop for Compound<'a, W> { fn drop(&mut self) { if let Some(limit) = &mut self.ser.recursion_limit { @@ -1340,6 +1643,46 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { where T: ?Sized + Serialize, { + if let Some(ref mut range) = self.range { + if !range.fallback { + match RangeCompound::try_serialize_number(value) { + Some(s) => { + if range.first.is_none() { + range.first = Some(s); + } else { + range.second = Some(s); + } + return Ok(()); + } + None => { + range.fallback = true; + } + } + self.ser.output.write_char('(')?; + if !self.ser.compact_structs() { + self.ser.is_empty = Some(false); + self.ser.start_indent()?; + } + + let first_key = match range.kind { + RangeKind::RangeTo | RangeKind::RangeToInclusive => "end", + _ => "start", + }; + + // Collect buffered values before the mutable borrow below + let buffered: [Option<(&'static str, crate::value::Number)>; 2] = [ + range.first.take().map(|v| (first_key, v)), + range.second.take().map(|v| ("end", v)), + ]; + + let _ = drop(range); // release borrow of self.range + for (bkey, bval) in buffered.into_iter().flatten() { + ser::SerializeStruct::serialize_field(self, bkey, &bval)?; + } + // fall through to emit the current non-numeric field normally + } + } + let mut restore_field = self.ser.pretty.as_mut().and_then(|(config, _)| { config.path_meta.take().map(|mut field| { if let Some(fields) = field.fields_mut() { @@ -1402,7 +1745,43 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { Ok(()) } - fn end(self) -> Result<()> { + fn end(mut self) -> Result<()> { + if let Some(ref mut range) = self.range { + if !range.fallback { + // All fields were numeric — emit compact syntax + let sep = match range.kind { + RangeKind::RangeInclusive | RangeKind::RangeToInclusive => "..=", + _ => "..", + }; + + match range.kind { + RangeKind::RangeFrom => { + if let Some(start) = range.first.take() { + start.serialize(&mut *self.ser)?; + } + self.ser.output.write_str("..")?; + } + RangeKind::RangeTo | RangeKind::RangeToInclusive => { + self.ser.output.write_str(sep)?; + if let Some(end) = range.first.take() { + end.serialize(&mut *self.ser)?; + } + } + RangeKind::Range | RangeKind::RangeInclusive => { + if let Some(start) = range.first.take() { + start.serialize(&mut *self.ser)?; + } + self.ser.output.write_str(sep)?; + if let Some(end) = range.second.take() { + end.serialize(&mut *self.ser)?; + } + } + } + return Ok(()); + } + // fallback: close the struct normally (end_indent + ')' handled below) + } + if let State::Rest = self.state { if let Some((ref config, ref pretty)) = self.ser.pretty { if pretty.indent <= config.depth_limit && !config.compact_structs { diff --git a/src/value/mod.rs b/src/value/mod.rs index 9735b1af0..9df6a96d1 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -255,6 +255,25 @@ impl<'a, 'de> MapAccess<'de> for MapAccessor<'a> { } } +pub(crate) struct NumberDeserializer(pub(crate) Number); + +impl<'de> serde::de::Deserializer<'de> for NumberDeserializer { + type Error = crate::error::Error; + + fn deserialize_any>( + self, + visitor: V, + ) -> crate::error::Result { + self.0.visit(visitor) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 + char str string bytes byte_buf option unit unit_struct + newtype_struct seq tuple tuple_struct map struct enum identifier ignored_any + } +} + #[cfg(test)] mod tests { use alloc::{collections::BTreeMap, vec}; diff --git a/tests/601_support_for_number_ranges.rs b/tests/601_support_for_number_ranges.rs new file mode 100644 index 000000000..98e7df90f --- /dev/null +++ b/tests/601_support_for_number_ranges.rs @@ -0,0 +1,194 @@ +use ron; +use serde::{Deserialize, Serialize}; + +// NOTE: +// std::ops::RangeToInclusive and RangeFull do not have stable serde support out of the box. +// We use `serde(remote = "...")` to define how these std types should be serialized/deserialized. + +#[derive(PartialEq, Deserialize, Serialize, Debug)] +#[serde(remote = "std::ops::RangeToInclusive")] +struct RangeToInclusive { + end: T, +} + +#[derive(PartialEq, Deserialize, Serialize, Debug)] +#[serde(remote = "std::ops::RangeFull")] +struct RangeFull; + +#[derive(PartialEq, Deserialize, Serialize, Debug)] +struct RangeTest { + a: std::ops::Range, + b: std::ops::RangeInclusive, + c: std::ops::Range, + d: std::ops::RangeInclusive, +} + +#[test] +fn test_ranges() { + let ranges = RangeTest { + a: 0..5, + b: 1..=3, + c: 0.6..4.3, + d: 0.3..=5.7, + }; + + let ser = ron::to_string(&ranges).unwrap(); + assert_eq!( + ser, + "(a:(start:0,end:5),b:(start:1,end:3),c:(start:0.6,end:4.3),d:(start:0.3,end:5.7))" + ); + + assert_eq!( + ron::ser::to_string_pretty( + &ranges, + ron::ser::PrettyConfig::new() + .compact_ranges(true) + .new_line("") + .indentor("") + .separator("") + .compact_structs(true) + ) + .unwrap(), + "(a:0..5,b:1..=3,c:0.6..4.3,d:0.3..=5.7)" + ); + + let de: RangeTest = ron::from_str(&ser).unwrap(); + assert_eq!(de, ranges); +} + +#[test] +fn test_range_integer_bases() { + assert_eq!( + ron::from_str::>("0b0000..0b0101").unwrap(), + 0..5 + ); + assert_eq!( + ron::from_str::>("0o0..0o5").unwrap(), + 0..5 + ); + assert_eq!( + ron::from_str::>("0x0..0x5").unwrap(), + 0..5 + ); + + assert_eq!( + ron::from_str::>("b'\\x00'..b'\\x05'").unwrap(), + 0..5 + ); + assert_eq!( + ron::from_str::>("b'A'..=b'Z'").unwrap(), + b'A'..=b'Z' + ); + + assert_eq!( + ron::from_str::>("..0b0101").unwrap(), + ..5 + ); + assert_eq!( + ron::from_str::>("0b0000..").unwrap(), + 0.. + ); +} + +#[derive(PartialEq, Deserialize, Serialize, Debug)] +#[serde(untagged)] +enum MaybeRange { + Range(std::ops::Range), + RangeFrom(std::ops::RangeFrom), + Value(i32), +} + +#[test] +fn test_range_untagged() { + assert_eq!( + ron::from_str::("0..5").unwrap(), + MaybeRange::Range(0..5) + ); + assert_eq!( + ron::from_str::("0..").unwrap(), + MaybeRange::RangeFrom(0..) + ); + assert_eq!( + ron::from_str::("42").unwrap(), + MaybeRange::Value(42) + ); +} + +#[derive(PartialEq, Deserialize, Serialize, Debug)] +struct UnclosedRangeTest { + a: std::ops::RangeFrom, + b: std::ops::RangeTo, + #[serde(with = "RangeToInclusive")] + c: std::ops::RangeToInclusive, + d: std::ops::RangeFrom, + e: std::ops::RangeTo, + #[serde(with = "RangeToInclusive")] + f: std::ops::RangeToInclusive, + #[serde(with = "RangeFull")] + g: std::ops::RangeFull, +} + +#[test] +fn test_unclosed_ranges() { + let ranges = UnclosedRangeTest { + a: 2.., + b: ..3, + c: ..=3, + d: 1.5.., + e: ..2.3, + f: ..=2.3, + g: .., + }; + + let ser = ron::to_string(&ranges).unwrap(); + assert_eq!( + ser, + "(a:(start:2),b:(end:3),c:(end:3),d:(start:1.5),e:(end:2.3),f:(end:2.3),g:())" + ); + + assert_eq!( + ron::ser::to_string_pretty( + &ranges, + ron::ser::PrettyConfig::new() + .compact_ranges(true) + .new_line("") + .indentor("") + .separator("") + .compact_structs(true) + ) + .unwrap(), + "(a:2..,b:..3,c:..=3,d:1.5..,e:..2.3,f:..=2.3,g:..)" + ); + + let de: UnclosedRangeTest = ron::from_str(&ser).unwrap(); + assert_eq!(de, ranges); +} + +#[test] +fn test_string_range() { + assert!(ron::from_str::>("\"x\"..\"h\"").is_err()); + assert!(ron::from_str::>("\"x\"..\"h\"").is_err()); +} + +#[test] +fn test_inf_nan_ranges() { + let r = ron::from_str::>("inff32..").unwrap(); + assert!(r.start.is_infinite() && r.start.is_sign_positive()); + + let r = ron::from_str::>("NaNf32..").unwrap(); + assert!(r.start.is_nan()); + + let r = ron::from_str::>("inff64..").unwrap(); + assert!(r.start.is_infinite() && r.start.is_sign_positive()); + + let r = ron::from_str::>("NaNf64..").unwrap(); + assert!(r.start.is_nan()); + + let r = ron::from_str::>("(start:inf,end:NaN)").unwrap(); + assert!(r.start.is_infinite() && r.start.is_sign_positive()); + assert!(r.end.is_nan()); + + let r = ron::from_str::>("(start:NaN,end:inf)").unwrap(); + assert!(r.start().is_nan()); + assert!(r.end().is_infinite()); +}