Merge pull request #248 from ehuss/float-0.5

0.5: Support floats nan, inf, and +/-0.0.
This commit is contained in:
Alex Crichton 2018-07-11 09:01:53 -05:00 committed by GitHub
commit 816774077c
8 changed files with 117 additions and 44 deletions

View file

@ -6,6 +6,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::error; use std::error;
use std::f64;
use std::fmt; use std::fmt;
use std::str; use std::str;
use std::vec; use std::vec;
@ -882,6 +883,14 @@ impl<'a> Deserializer<'a> {
} }
_ => Err(self.error(at, ErrorKind::NumberInvalid)), _ => Err(self.error(at, ErrorKind::NumberInvalid)),
} }
} else if s == "inf" {
Ok(Value { e: E::Float(f64::INFINITY), start: start, end: end })
} else if s == "-inf" {
Ok(Value { e: E::Float(f64::NEG_INFINITY), start: start, end: end })
} else if s == "nan" {
Ok(Value { e: E::Float(f64::NAN), start: start, end: end })
} else if s == "-nan" {
Ok(Value { e: E::Float(-f64::NAN), start: start, end: end })
} else { } else {
self.integer(s).map(|f| Value { e: E::Integer(f), start: start, end: end }) self.integer(s).map(|f| Value { e: E::Integer(f), start: start, end: end })
} }

View file

@ -737,6 +737,27 @@ impl<'a> Serializer<'a> {
} }
} }
macro_rules! serialize_float {
($this:expr, $v:expr) => {{
$this.emit_key("float")?;
if ($v.is_nan() || $v == 0.0) && $v.is_sign_negative() {
drop(write!($this.dst, "-"));
}
if $v.is_nan() {
drop(write!($this.dst, "nan"));
} else {
drop(write!($this.dst, "{}", $v));
}
if $v % 1.0 == 0.0 {
drop(write!($this.dst, ".0"));
}
if let State::Table { .. } = $this.state {
$this.dst.push_str("\n");
}
return Ok(());
}};
}
impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -785,35 +806,11 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> {
} }
fn serialize_f32(self, v: f32) -> Result<(), Self::Error> { fn serialize_f32(self, v: f32) -> Result<(), Self::Error> {
if !v.is_finite() { serialize_float!(self, v)
return Err(Error::NumberInvalid);
}
self.emit_key("float")?;
drop(write!(self.dst, "{}", v));
if v % 1.0 == 0.0 {
drop(write!(self.dst, ".0"));
}
if let State::Table { .. } = self.state {
self.dst.push_str("\n");
}
Ok(())
} }
fn serialize_f64(self, v: f64) -> Result<(), Self::Error> { fn serialize_f64(self, v: f64) -> Result<(), Self::Error> {
if !v.is_finite() { serialize_float!(self, v)
return Err(Error::NumberInvalid);
}
self.emit_key("float")?;
drop(write!(self.dst, "{}", v));
if v % 1.0 == 0.0 {
drop(write!(self.dst, ".0"));
}
if let State::Table { .. } = self.state {
self.dst.push_str("\n");
}
Ok(())
} }
fn serialize_char(self, v: char) -> Result<(), Self::Error> { fn serialize_char(self, v: char) -> Result<(), Self::Error> {

View file

@ -4,5 +4,6 @@ use rustc_version::{version, Version};
fn main() { fn main() {
if version().unwrap() >= Version::parse("1.20.0").unwrap() { if version().unwrap() >= Version::parse("1.20.0").unwrap() {
println!(r#"cargo:rustc-cfg=feature="test-quoted-keys-in-macro""#); println!(r#"cargo:rustc-cfg=feature="test-quoted-keys-in-macro""#);
println!(r#"cargo:rustc-cfg=feature="test-nan-sign""#);
} }
} }

79
test-suite/tests/float.rs Normal file
View file

@ -0,0 +1,79 @@
extern crate toml;
#[macro_use]
extern crate serde_derive;
use toml::Value;
macro_rules! float_inf_tests {
($ty:ty) => {{
#[derive(Serialize, Deserialize)]
struct S {
sf1: $ty,
sf2: $ty,
sf3: $ty,
sf4: $ty,
sf5: $ty,
sf6: $ty,
sf7: $ty,
sf8: $ty,
}
let inf: S = toml::from_str(
r"
# infinity
sf1 = inf # positive infinity
sf2 = +inf # positive infinity
sf3 = -inf # negative infinity
# not a number
sf4 = nan # actual sNaN/qNaN encoding is implementation specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation specific
# zero
sf7 = +0.0
sf8 = -0.0
").expect("Parse infinities.");
assert!(inf.sf1.is_infinite());
assert!(inf.sf1.is_sign_positive());
assert!(inf.sf2.is_infinite());
assert!(inf.sf2.is_sign_positive());
assert!(inf.sf3.is_infinite());
assert!(inf.sf3.is_sign_negative());
assert!(inf.sf4.is_nan());
assert!(inf.sf4.is_sign_positive());
assert!(inf.sf5.is_nan());
assert!(inf.sf5.is_sign_positive());
assert!(inf.sf6.is_nan());
assert!(inf.sf6.is_sign_negative());
assert_eq!(inf.sf7, 0.0);
assert!(inf.sf7.is_sign_positive());
assert_eq!(inf.sf8, 0.0);
assert!(inf.sf8.is_sign_negative());
let s = toml::to_string(&inf).unwrap();
assert_eq!(
s, "\
sf1 = inf
sf2 = inf
sf3 = -inf
sf4 = nan
sf5 = nan
sf6 = -nan
sf7 = 0.0
sf8 = -0.0
"
);
toml::from_str::<Value>(&s).expect("roundtrip");
}};
}
#[test]
#[cfg(feature = "test-nan-sign")]
fn float_inf() {
float_inf_tests!(f32);
float_inf_tests!(f64);
}

View file

@ -1,14 +0,0 @@
extern crate toml;
use std::f64;
#[test]
fn test_invalid_float_encode() {
fn bad(value: toml::Value) {
assert!(toml::to_string(&value).is_err());
}
bad(toml::Value::Float(f64::INFINITY));
bad(toml::Value::Float(f64::NEG_INFINITY));
bad(toml::Value::Float(f64::NAN));
}

View file

@ -10,8 +10,5 @@ fn bad() {
bad("a = 1__1"); bad("a = 1__1");
bad("a = 1_"); bad("a = 1_");
bad("''"); bad("''");
bad("a = nan");
bad("a = -inf");
bad("a = inf");
bad("a = 9e99999"); bad("a = 9e99999");
} }

View file

@ -1,4 +1,6 @@
{ {
"answer": {"type": "integer", "value": "42"}, "answer": {"type": "integer", "value": "42"},
"neganswer": {"type": "integer", "value": "-42"} "neganswer": {"type": "integer", "value": "-42"},
"neg_zero": {"type": "integer", "value": "0"},
"pos_zero": {"type": "integer", "value": "0"}
} }

View file

@ -1,2 +1,4 @@
answer = 42 answer = 42
neganswer = -42 neganswer = -42
neg_zero = -0
pos_zero = +0