Repository: maciejhirsz/json-rust (rust)

Issue:
New codegen broken for some floats.
`assert_eq!(stringify(0.00001), "0.00001");` fails.

Pull request:
0.7.2
Fixes #25 

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index ff591a4..acc06e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "json"
-version = "0.7.1"
+version = "0.7.2"
 authors = ["Maciej Hirsz <maciej.hirsz@gmail.com>"]
 description = "JSON implementation in Rust"
 repository = "https://github.com/maciejhirsz/json-rust"
diff --git a/README.md b/README.md
index 9d6142e..2d8b5a9 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@
 
 Parse and serialize [JSON](http://json.org/) with ease.
 
-**[Complete Documentation](http://terhix.com/doc/json/) - [Cargo](https://crates.io/crates/json) - [Repository](https://github.com/maciejhirsz/json-rust)**
+**[Complete Documentation](http://terhix.com/doc/json/) -**
+**[Cargo](https://crates.io/crates/json) -**
+**[Repository](https://github.com/maciejhirsz/json-rust)**
 
 ## Why?
 
diff --git a/src/codegen.rs b/src/codegen.rs
index 53e3438..53e2ddd 100644
--- a/src/codegen.rs
+++ b/src/codegen.rs
@@ -36,8 +36,8 @@ pub trait Generator {
 
     fn write_digits_from_u64(&mut self, mut num: u64, length: &mut u8) {
         let digit = (num % 10) as u8;
-        num /= 10;
-        if num > 0 {
+        if num > 9 {
+            num /= 10;
             self.write_digits_from_u64(num, length);
         }
         *length += 1;
@@ -54,7 +54,7 @@ pub trait Generator {
         self.write_digits_from_u64(num as u64, &mut length);
 
         let mut fract = num.fract();
-        if fract < 1e-10 {
+        if fract < 1e-16 {
             return;
         }
 
@@ -64,7 +64,7 @@ pub trait Generator {
         fract = fract.fract();
         length += 2;
 
-        while length < 17 && fract > 0.01 {
+        while length < 17 && fract > 1e-15 {
             fract *= 10.0;
             self.write_char((fract as u8) + b'0');
             fract = fract.fract();
diff --git a/src/lib.rs b/src/lib.rs
index 3d1fa88..456860d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,7 +4,9 @@
 //!
 //! Parse and serialize [JSON](http://json.org/) with ease.
 //!
-//! **[Complete Documentation](http://terhix.com/doc/json/) - [Cargo](https://crates.io/crates/json) - [Repository](https://github.com/maciejhirsz/json-rust)**
+//! **[Complete Documentation](http://terhix.com/doc/json/) -**
+//! **[Cargo](https://crates.io/crates/json) -**
+//! **[Repository](https://github.com/maciejhirsz/json-rust)**
 //!
 //! ## Why?
 //!
@@ -244,6 +246,11 @@ impl fmt::Display for JsonValue {
     }
 }
 
+/// Convenience for `JsonValue::from(value)`
+pub fn from<T>(value: T) -> JsonValue where T: Into<JsonValue> {
+    value.into()
+}
+
 #[deprecated(since="0.5.0", note="Use `value.dump(0)` instead")]
 pub fn stringify_ref(root: &JsonValue) -> String {
     root.dump()
@@ -262,6 +269,7 @@ pub fn stringify_pretty<T>(root: T, spaces: u16) -> String where T: Into<JsonVal
     root.pretty(spaces)
 }
 
+
 #[macro_export]
 macro_rules! array {
     [] => ($crate::JsonValue::new_array());
@@ -307,18 +315,20 @@ macro_rules! implement_extras {
 
         impl From<Vec<$from>> for JsonValue {
             fn from(mut val: Vec<$from>) -> JsonValue {
-                JsonValue::Array(val.drain(..)
-                    .map(|value| value.into())
-                    .collect::<Vec<JsonValue>>()
+                JsonValue::Array(
+                    val.drain(..)
+                       .map(|value| value.into())
+                       .collect()
                 )
             }
         }
 
         impl From<Vec<Option<$from>>> for JsonValue {
             fn from(mut val: Vec<Option<$from>>) -> JsonValue {
-                JsonValue::Array(val.drain(..)
-                    .map(|item| item.into())
-                    .collect::<Vec<JsonValue>>()
+                JsonValue::Array(
+                    val.drain(..)
+                       .map(|item| item.into())
+                       .collect()
                 )
             }
         }
diff --git a/src/parser.rs b/src/parser.rs
index 249963c..c164840 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -19,12 +19,14 @@ pub enum Token {
 }
 
 macro_rules! expect_char {
-    ($tok:ident, $ch:pat) => {
-        match $tok.source.next() {
-            Some($ch) => {},
-            Some(ch)  => return Err(JsonError::unexpected_character(ch)),
-            None      => return Err(JsonError::UnexpectedEndOfJson)
-        }
+    ($tok:ident, $( $ch:pat ),*) => {
+        $(
+            match $tok.source.next() {
+                Some($ch) => {},
+                Some(ch)  => return Err(JsonError::unexpected_character(ch)),
+                None      => return Err(JsonError::UnexpectedEndOfJson)
+            }
+        )*
     }
 }
 
@@ -205,22 +207,15 @@ impl<'a> Tokenizer<'a> {
                 b'"' => Token::String(try!(self.read_string())),
                 b'0' ... b'9' | b'-' => Token::Number(try!(self.read_number(ch))),
                 b't' => {
-                    expect_char!(self, b'r');
-                    expect_char!(self, b'u');
-                    expect_char!(self, b'e');
+                    expect_char!(self, b'r', b'u', b'e');
                     Token::Boolean(true)
                 },
                 b'f' => {
-                    expect_char!(self, b'a');
-                    expect_char!(self, b'l');
-                    expect_char!(self, b's');
-                    expect_char!(self, b'e');
+                    expect_char!(self, b'a', b'l', b's', b'e');
                     Token::Boolean(false)
                 },
                 b'n' => {
-                    expect_char!(self, b'u');
-                    expect_char!(self, b'l');
-                    expect_char!(self, b'l');
+                    expect_char!(self, b'u', b'l', b'l');
                     Token::Null
                 },
                 // whitespace
@@ -326,13 +321,15 @@ impl<'a> Parser<'a> {
 
     fn value_from(&mut self, token: Token) -> JsonResult<JsonValue> {
         Ok(match token {
-            Token::String(value)  => JsonValue::String(value),
-            Token::Number(value)  => JsonValue::Number(value),
-            Token::Boolean(value) => JsonValue::Boolean(value),
-            Token::Null           => JsonValue::Null,
-            Token::BracketOn      => return self.array(),
-            Token::BraceOn        => return self.object(),
-            token => return Err(JsonError::unexpected_token(token))
+            Token::String(value)    => JsonValue::String(value),
+            Token::Number(value)    => JsonValue::Number(value),
+            Token::Boolean(value)   => JsonValue::Boolean(value),
+            Token::Null             => JsonValue::Null,
+            Token::BracketOn        => return self.array(),
+            Token::BraceOn          => return self.object(),
+            token                   => {
+                return Err(JsonError::unexpected_token(token))
+            }
         })
     }
 
diff --git a/src/value.rs b/src/value.rs
index fc1e59e..4eaba76 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -62,8 +62,60 @@ impl JsonValue {
         }
     }
 
-    /// Deprecated because the return type is planned to change to
-    /// `Option<String>` eventually down the road.
+    pub fn is_number(&self) -> bool {
+        match *self {
+            JsonValue::Number(_) => true,
+            _                    => false,
+        }
+    }
+
+    pub fn is_boolean(&self) -> bool {
+        match *self {
+            JsonValue::Boolean(_) => true,
+            _                     => false
+        }
+    }
+
+    pub fn is_null(&self) -> bool {
+        match *self {
+            JsonValue::Null => true,
+            _               => false,
+        }
+    }
+
+    pub fn is_object(&self) -> bool {
+        match *self {
+            JsonValue::Object(_) => true,
+            _                    => false,
+        }
+    }
+
+    pub fn is_array(&self) -> bool {
+        match *self {
+            JsonValue::Array(_) => true,
+            _                   => false,
+        }
+    }
+
+    /// Checks whether the value is empty. Returns true for:
+    ///
+    /// - empty string (`""`)
+    /// - number `0`
+    /// - boolean `false`
+    /// - null
+    /// - empty array (`array![]`)
+    /// - empty object (`object!{}`)
+    pub fn is_empty(&self) -> bool {
+        match *self {
+            JsonValue::String(ref value)  => value.is_empty(),
+            JsonValue::Number(ref value)  => !value.is_normal(),
+            JsonValue::Boolean(ref value) => !value,
+            JsonValue::Null               => true,
+            JsonValue::Array(ref value)   => value.is_empty(),
+            JsonValue::Object(ref value)  => value.is_empty(),
+        }
+    }
+
     #[deprecated(since="0.6.1", note="Use `as_str` instead")]
     pub fn as_string(&self) -> JsonResult<&String> {
         match *self {
@@ -79,13 +131,6 @@ impl JsonValue {
         }
     }
 
-    pub fn is_number(&self) -> bool {
-        match *self {
-            JsonValue::Number(_) => true,
-            _                    => false,
-        }
-    }
-
     #[deprecated(since="0.6.1", note="Use `as_f64` instead")]
     pub fn as_number(&self) -> JsonResult<&f64> {
         match *self {
@@ -145,10 +190,10 @@ impl JsonValue {
         self.as_f64().and_then(|value| f64_to_singed!(isize, value))
     }
 
-    pub fn is_boolean(&self) -> bool {
+    pub fn as_bool(&self) -> Option<bool> {
         match *self {
-            JsonValue::Boolean(_) => true,
-            _                     => false
+            JsonValue::Boolean(ref value) => Some(*value),
+            _                             => None
         }
     }
 
@@ -160,34 +205,6 @@ impl JsonValue {
         }
     }
 
-    pub fn as_bool(&self) -> Option<bool> {
-        match *self {
-            JsonValue::Boolean(ref value) => Some(*value),
-            _                             => None
-        }
-    }
-
-    pub fn is_null(&self) -> bool {
-        match *self {
-            JsonValue::Null => true,
-            _               => false,
-        }
-    }
-
-    pub fn is_object(&self) -> bool {
-        match *self {
-            JsonValue::Object(_) => true,
-            _                    => false,
-        }
-    }
-
-    pub fn is_array(&self) -> bool {
-        match *self {
-            JsonValue::Array(_) => true,
-            _                   => false,
-        }
-    }
-
     /// Works on `JsonValue::Object` - create or override key with value.
     #[must_use]
     #[deprecated(since="0.6.0", note="Use `object[key] = value.into()` instead")]
@@ -262,6 +279,17 @@ impl JsonValue {
         }
     }
 
+    /// Works on `JsonValue::Array` - remove and return last element from
+    /// an array. On failure returns a null.
+    pub fn pop(&mut self) -> JsonValue {
+        match *self {
+            JsonValue::Array(ref mut vec) => {
+                vec.pop().unwrap_or(JsonValue::Null)
+            },
+            _ => JsonValue::Null
+        }
+    }
+
     /// Works on `JsonValue::Array` - gets a reference to a value at index.
     /// For most purposes consider using `array[index]` instead.
     #[deprecated(since="0.6.0", note="Use `array[index]` instead")]
@@ -358,6 +386,29 @@ impl JsonValue {
             _ => EntriesMut::None
         }
     }
+
+    /// Works on `JsonValue::Object` - remove a key and return the value it held.
+    /// If the key was not present, the method is called on anything but an
+    /// object, it will return a null.
+    pub fn remove(&mut self, key: &str) -> JsonValue {
+        match *self {
+            JsonValue::Object(ref mut btree) => {
+                btree.remove(key).unwrap_or(JsonValue::Null)
+            },
+            _ => JsonValue::Null
+        }
+    }
+
+    /// When called on an array or an object, will wipe them clean. When called
+    /// on a string will clear the string. Numbers and booleans become null.
+    pub fn clear(&mut self) {
+        match *self {
+            JsonValue::String(ref mut string) => string.clear(),
+            JsonValue::Object(ref mut btree)  => btree.clear(),
+            JsonValue::Array(ref mut vec)     => vec.clear(),
+            _                                 => *self = JsonValue::Null,
+        }
+    }
 }
 
 /// Implements indexing by `usize` to easily access array members:
@@ -411,7 +462,7 @@ impl IndexMut<usize> for JsonValue {
             _ => {
                 *self = JsonValue::new_array();
                 self.push(JsonValue::Null).unwrap();
-                &mut self[0]
+                self.index_mut(index)
             }
         }
     }
@@ -470,7 +521,7 @@ impl<'a> IndexMut<&'a str> for JsonValue {
             },
             _ => {
                 *self = JsonValue::new_object();
-                &mut self[index]
+                self.index_mut(index)
             }
         }
     }


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 6e0a133..86d1ac9 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -76,12 +76,28 @@ fn is_false() {
 }
 
 #[test]
-fn is_nul() {
+fn is_null() {
     let null = JsonValue::Null;
 
     assert!(null.is_null());
 }
 
+#[test]
+fn is_empty() {
+    assert!(Null.is_empty());
+    assert!(json::from(0).is_empty());
+    assert!(json::from("").is_empty());
+    assert!(json::from(false).is_empty());
+    assert!(array![].is_empty());
+    assert!(object!{}.is_empty());
+
+    assert!(!json::from(1).is_empty());
+    assert!(!json::from("foo").is_empty());
+    assert!(!json::from(true).is_empty());
+    assert!(!array![0].is_empty());
+    assert!(!object!{ "foo" => false }.is_empty());
+}
+
 #[test]
 fn stringify_null() {
     assert_eq!(stringify(Null), "null");
@@ -119,6 +135,11 @@ fn stringify_integer() {
     assert_eq!(stringify(42), "42");
 }
 
+#[test]
+fn stringify_small_number() {
+    assert_eq!(stringify(0.000000000000001), "0.000000000000001");
+}
+
 #[test]
 fn stringify_true() {
     assert_eq!(stringify(true), "true");
@@ -238,34 +259,6 @@ fn stringify_pretty_object() {
                 \"Brutus\",\n    \"mother\": \"Helga\"\n  }\n}");
 }
 
-#[test]
-fn object_dump_minified() {
-    let object = object!{
-        "name" => "Maciej",
-        "age" => 30
-    };
-
-    assert_eq!(object.dump(), "{\"age\":30,\"name\":\"Maciej\"}");
-}
-
-#[test]
-fn object_dump_pretty() {
-    let object = object!{
-        "name" => "Urlich",
-        "age" => 50,
-        "parents" => object!{
-            "mother" => "Helga",
-            "father" => "Brutus"
-        },
-        "cars" => array![ "Golf", "Mercedes", "Porsche" ]
-    };
-
-    assert_eq!(object.pretty(2),
-               "{\n  \"age\": 50,\n  \"cars\": [\n    \"Golf\",\n    \"Mercedes\",\n    \
-                \"Porsche\"\n  ],\n  \"name\": \"Urlich\",\n  \"parents\": {\n    \"father\": \
-                \"Brutus\",\n    \"mother\": \"Helga\"\n  }\n}");
-}
-
 #[test]
 fn parse_true() {
     assert_eq!(parse("true").unwrap(), true);
@@ -473,16 +466,6 @@ fn parse_error() {
     assert!(parse("10 20").is_err());
 }
 
-#[test]
-fn object_len() {
-    let data = object!{
-        "a" => true,
-        "b" => false
-    };
-
-    assert_eq!(data.len(), 2);
-}
-
 #[test]
 fn array_len() {
     let data = array![0, 1, 2, 3];
@@ -505,14 +488,72 @@ fn array_contains() {
 }
 
 #[test]
-fn null_len() {
-    let data = json::Null;
+fn array_push() {
+    let mut data = array![1, 2];
 
-    assert_eq!(data.len(), 0);
+    data.push(3).unwrap();
+
+    assert_eq!(data, array![1, 2, 3]);
+}
+
+#[test]
+fn array_pop() {
+    let mut data = array![1, 2, 3];
+
+    assert_eq!(data.pop(), 3);
+    assert_eq!(data, array![1, 2]);
+}
+
+#[test]
+fn array_members() {
+    let data = array![1, "foo"];
+
+    for member in data.members() {
+        assert!(!member.is_null());
+    }
+
+    let mut members = data.members();
+
+    assert_eq!(members.next().unwrap(), 1);
+    assert_eq!(members.next().unwrap(), "foo");
+    assert!(members.next().is_none());
+}
+
+#[test]
+fn array_members_mut() {
+    let mut data = array![Null, Null];
+
+    for member in data.members_mut() {
+        assert!(member.is_null());
+        *member = 100.into();
+    }
+
+    assert_eq!(data, array![100, 100]);
+}
+
+#[test]
+fn object_len() {
+    let data = object!{
+        "a" => true,
+        "b" => false
+    };
+
+    assert_eq!(data.len(), 2);
 }
 
 #[test]
-fn iter_entries() {
+fn object_remove() {
+    let mut data = object!{
+        "foo" => "bar",
+        "answer" => 42
+    };
+
+    assert_eq!(data.remove("foo"), "bar");
+    assert_eq!(data, object!{ "answer" => 42 });
+}
+
+#[test]
+fn object_entries() {
     let data = object!{
         "a" => 1,
         "b" => "foo"
@@ -536,7 +577,7 @@ fn iter_entries() {
 }
 
 #[test]
-fn iter_entries_mut() {
+fn object_entries_mut() {
     let mut data = object!{
         "a" => Null,
         "b" => Null
@@ -554,30 +595,38 @@ fn iter_entries_mut() {
 }
 
 #[test]
-fn iter_members() {
-    let data = array![1, "foo"];
+fn object_dump_minified() {
+    let object = object!{
+        "name" => "Maciej",
+        "age" => 30
+    };
 
-    for member in data.members() {
-        assert!(!member.is_null());
-    }
+    assert_eq!(object.dump(), "{\"age\":30,\"name\":\"Maciej\"}");
+}
 
-    let mut members = data.members();
+#[test]
+fn object_dump_pretty() {
+    let object = object!{
+        "name" => "Urlich",
+        "age" => 50,
+        "parents" => object!{
+            "mother" => "Helga",
+            "father" => "Brutus"
+        },
+        "cars" => array![ "Golf", "Mercedes", "Porsche" ]
+    };
 
-    assert_eq!(members.next().unwrap(), 1);
-    assert_eq!(members.next().unwrap(), "foo");
-    assert!(members.next().is_none());
+    assert_eq!(object.pretty(2),
+               "{\n  \"age\": 50,\n  \"cars\": [\n    \"Golf\",\n    \"Mercedes\",\n    \
+                \"Porsche\"\n  ],\n  \"name\": \"Urlich\",\n  \"parents\": {\n    \"father\": \
+                \"Brutus\",\n    \"mother\": \"Helga\"\n  }\n}");
 }
 
 #[test]
-fn iter_members_mut() {
-    let mut data = array![Null, Null];
-
-    for member in data.members_mut() {
-        assert!(member.is_null());
-        *member = 100.into();
-    }
+fn null_len() {
+    let data = json::Null;
 
-    assert_eq!(data, array![100, 100]);
+    assert_eq!(data.len(), 0);
 }
 
 #[test]