Merge remote-tracking branch 'origin/upstream/master'

Change-Id: I538b5b4eac31295c0f0942e1e26a7da68976fc9f
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0219beb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "serde_json5"
+version = "0.1.0"
+authors = ["Gary Bressler <geb@google.com>"]
+description = "A Serde (de)serializer for JSON5."
+license = "Apache-2.0"
+repository = "https://github.com/google/serde_json5"
+readme = "README.md"
+keywords = ["json5", "json", "serialize", "serializer", "deserialize", "deserializer", "parse", "parser"]
+edition = "2018"
+
+[lib]
+name = "serde_json5"
+path = "third_party/src/lib.rs"
+
+[[test]]
+name = "serde_json5_test"
+path = "third_party/tests/lib.rs"
+
+[dependencies]
+pest = "2.0"
+pest_derive = "2.0"
+serde = "1.0"
+
+[dev-dependencies]
+matches = "0.1.8"
+serde_derive = "1.0"
+serde_json = "1.0"
diff --git a/third_party/Cargo.toml b/third_party/Cargo.toml
deleted file mode 100644
index e1577d8..0000000
--- a/third_party/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "json5"
-version = "0.2.8"
-authors = ["Callum Oakley <hello@callumoakley.net>"]
-description = "A Rust JSON5 serializer and deserializer which speaks Serde."
-license = "ISC"
-repository = "https://github.com/callum-oakley/json5-rs"
-readme = "README.md"
-keywords = ["json5", "parse", "parser", "serde", "json"]
-edition = "2018"
-
-[dependencies]
-pest = "2.0"
-pest_derive = "2.0"
-serde = "1.0"
-
-[dev-dependencies]
-matches = "0.1.8"
-serde_derive = "1.0"
-serde_json = "1.0"
diff --git a/third_party/src/de.rs b/third_party/src/de.rs
index bfe06ec..a1608be 100644
--- a/third_party/src/de.rs
+++ b/third_party/src/de.rs
@@ -6,11 +6,137 @@
 use std::char;
 use std::collections::VecDeque;
 use std::f64;
+use std::io::Read;
 
 use crate::error::{self, Error, Result};
 
 #[derive(Parser)]
-#[grammar = "json5.pest"]
+#[grammar_inline = r#"
+// see https://spec.json5.org/#syntactic-grammar and
+// https://spec.json5.org/#lexical-grammar
+
+COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" | "//" ~ (!line_terminator ~ ANY)* }
+
+WHITESPACE = _{
+  "\u{0009}" |
+  "\u{000B}" |
+  "\u{000C}" |
+  "\u{0020}" |
+  "\u{00A0}" |
+  "\u{FEFF}" |
+  SPACE_SEPARATOR |
+  line_terminator
+}
+
+array = { "[" ~ "]" | "[" ~ value ~ ("," ~ value)* ~ ","? ~ "]" }
+
+boolean = @{ "true" | "false" }
+
+char_escape_sequence = @{ single_escape_char | non_escape_char }
+
+char_literal = @{ !("\\" | line_terminator) ~ ANY }
+
+decimal_integer_literal = _{ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
+
+decimal_literal = _{
+  decimal_integer_literal ~ "." ~ ASCII_DIGIT* ~ exponent_part? |
+  "." ~ ASCII_DIGIT+~ exponent_part? |
+  decimal_integer_literal ~ exponent_part?
+}
+
+double_quote_char = _{
+  "\\" ~ escape_sequence |
+  line_continuation |
+  !"\"" ~ char_literal
+}
+
+escape_char = _{ single_escape_char | ASCII_DIGIT | "x" | "u" }
+
+escape_sequence = _{
+  char_escape_sequence |
+  nul_escape_sequence |
+  "x" ~ hex_escape_sequence |
+  "u" ~ unicode_escape_sequence
+}
+
+exponent_part = _{ ^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+ }
+
+hex_escape_sequence = @{ ASCII_HEX_DIGIT{2} }
+
+hex_integer_literal = _{ ^"0x" ~ ASCII_HEX_DIGIT+ }
+
+identifier = ${ identifier_start ~ identifier_part* }
+
+identifier_part = _{
+  identifier_start |
+  &(
+    NONSPACING_MARK |
+    DIACRITIC | // not sure about this, spec says "Combining spacing mark (Mc)"
+    DECIMAL_NUMBER |
+    CONNECTOR_PUNCTUATION |
+    "\u{200C}" |
+    "\u{200D}"
+  ) ~ char_literal
+}
+
+identifier_start = _{
+  &(unicode_letter | "$" | "_") ~ char_literal |
+  "\\u" ~ unicode_escape_sequence
+}
+
+key = _{ identifier | string }
+
+line_continuation = _{ "\\" ~ line_terminator_sequence }
+
+line_terminator = _{ "\u{000A}" | "\u{000D}" | "\u{2028}" | "\u{2029}" }
+
+line_terminator_sequence = _{ "\u{000D}" ~ "\u{000A}" | line_terminator }
+
+non_escape_char = _{ !(escape_char | line_terminator) ~ ANY }
+
+nul_escape_sequence = @{ "0" }
+
+null = @{ "null" }
+
+number = @{ ("+" | "-")? ~ numeric_literal }
+
+numeric_literal = _{
+  hex_integer_literal |
+  decimal_literal |
+  "Infinity" |
+  "NaN"
+}
+
+object = { "{" ~ "}" | "{" ~ pair ~ ("," ~ pair)* ~ ","? ~ "}" }
+
+pair = _{ key ~ ":" ~ value }
+
+single_escape_char = _{ "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" }
+
+single_quote_char = _{
+  "\\" ~ escape_sequence |
+  line_continuation |
+  !"'" ~ char_literal
+}
+
+string = ${ "\"" ~ double_quote_char* ~ "\"" | "'" ~ single_quote_char* ~ "'" }
+
+text = _{ SOI ~ value ~ EOI }
+
+unicode_escape_sequence = @{ ASCII_HEX_DIGIT{4} }
+
+unicode_letter = _{
+  UPPERCASE_LETTER |
+  LOWERCASE_LETTER |
+  TITLECASE_LETTER |
+  MODIFIER_LETTER |
+  OTHER_LETTER |
+  LETTER_NUMBER
+}
+
+value = _{ null | boolean | string | number | object | array }
+"#]
+
 struct Parser;
 
 /// Deserialize an instance of type `T` from a string of JSON5 text. Can fail if the input is
@@ -23,6 +149,31 @@
     T::deserialize(&mut deserializer)
 }
 
+/// Deserialize an instance of type `T` from a slice of JSON5 text. Can fail if the input is
+/// invalid JSON5, or doesn&rsquo;t match the structure of the target type.
+pub fn from_slice<'a, T>(s: &'a [u8]) -> Result<T>
+where
+    T: de::Deserialize<'a>,
+{
+    let valid_utf8 = std::str::from_utf8(s)?;
+    let mut deserializer = Deserializer::from_str(valid_utf8)?;
+    T::deserialize(&mut deserializer)
+}
+
+
+/// Deserialize an instance of type `T` from any implementation of Read.  Can fail if the input is
+/// invalid JSON5, or doesn&rsquo;t match the structure of the target type.
+pub fn from_reader<T, R>(reader: &mut R) -> Result<T>
+where
+    T: serde::de::DeserializeOwned,
+    R: Read
+{
+    let mut data = String::default();
+    reader.read_to_string(&mut data)?;
+    from_str(&data)
+}
+
+
 struct Deserializer<'de> {
     pair: Option<Pair<'de, Rule>>,
 }
diff --git a/third_party/src/error.rs b/third_party/src/error.rs
index 4da921c..162a780 100644
--- a/third_party/src/error.rs
+++ b/third_party/src/error.rs
@@ -47,6 +47,18 @@
     }
 }
 
+impl From<std::io::Error> for Error {
+    fn from(err: std::io::Error) -> Self {
+        Error::Message { msg: err.to_string(), location: None}
+    }
+}
+
+impl From<std::str::Utf8Error> for Error {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Error::Message { msg: err.to_string(), location: None}
+    }
+}
+
 impl ser::Error for Error {
     fn custom<T: Display>(msg: T) -> Self {
         Error::Message { msg: msg.to_string(), location: None }
diff --git a/third_party/src/json5.pest b/third_party/src/json5.pest
deleted file mode 100644
index 86d962e..0000000
--- a/third_party/src/json5.pest
+++ /dev/null
@@ -1,123 +0,0 @@
-// see https://spec.json5.org/#syntactic-grammar and
-// https://spec.json5.org/#lexical-grammar
-
-COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" | "//" ~ (!line_terminator ~ ANY)* }
-
-WHITESPACE = _{
-  "\u{0009}" |
-  "\u{000B}" |
-  "\u{000C}" |
-  "\u{0020}" |
-  "\u{00A0}" |
-  "\u{FEFF}" |
-  SPACE_SEPARATOR |
-  line_terminator
-}
-
-array = { "[" ~ "]" | "[" ~ value ~ ("," ~ value)* ~ ","? ~ "]" }
-
-boolean = @{ "true" | "false" }
-
-char_escape_sequence = @{ single_escape_char | non_escape_char }
-
-char_literal = @{ !("\\" | line_terminator) ~ ANY }
-
-decimal_integer_literal = _{ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
-
-decimal_literal = _{
-  decimal_integer_literal ~ "." ~ ASCII_DIGIT* ~ exponent_part? |
-  "." ~ ASCII_DIGIT+~ exponent_part? |
-  decimal_integer_literal ~ exponent_part?
-}
-
-double_quote_char = _{
-  "\\" ~ escape_sequence |
-  line_continuation |
-  !"\"" ~ char_literal
-}
-
-escape_char = _{ single_escape_char | ASCII_DIGIT | "x" | "u" }
-
-escape_sequence = _{
-  char_escape_sequence |
-  nul_escape_sequence |
-  "x" ~ hex_escape_sequence |
-  "u" ~ unicode_escape_sequence
-}
-
-exponent_part = _{ ^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+ }
-
-hex_escape_sequence = @{ ASCII_HEX_DIGIT{2} }
-
-hex_integer_literal = _{ ^"0x" ~ ASCII_HEX_DIGIT+ }
-
-identifier = ${ identifier_start ~ identifier_part* }
-
-identifier_part = _{
-  identifier_start |
-  &(
-    NONSPACING_MARK |
-    DIACRITIC | // not sure about this, spec says "Combining spacing mark (Mc)"
-    DECIMAL_NUMBER |
-    CONNECTOR_PUNCTUATION |
-    "\u{200C}" |
-    "\u{200D}"
-  ) ~ char_literal
-}
-
-identifier_start = _{
-  &(unicode_letter | "$" | "_") ~ char_literal |
-  "\\u" ~ unicode_escape_sequence
-}
-
-key = _{ identifier | string }
-
-line_continuation = _{ "\\" ~ line_terminator_sequence }
-
-line_terminator = _{ "\u{000A}" | "\u{000D}" | "\u{2028}" | "\u{2029}" }
-
-line_terminator_sequence = _{ "\u{000D}" ~ "\u{000A}" | line_terminator }
-
-non_escape_char = _{ !(escape_char | line_terminator) ~ ANY }
-
-nul_escape_sequence = @{ "0" }
-
-null = @{ "null" }
-
-number = @{ ("+" | "-")? ~ numeric_literal }
-
-numeric_literal = _{
-  hex_integer_literal |
-  decimal_literal |
-  "Infinity" |
-  "NaN"
-}
-
-object = { "{" ~ "}" | "{" ~ pair ~ ("," ~ pair)* ~ ","? ~ "}" }
-
-pair = _{ key ~ ":" ~ value }
-
-single_escape_char = _{ "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" }
-
-single_quote_char = _{
-  "\\" ~ escape_sequence |
-  line_continuation |
-  !"'" ~ char_literal
-}
-
-string = ${ "\"" ~ double_quote_char* ~ "\"" | "'" ~ single_quote_char* ~ "'" }
-
-text = _{ SOI ~ value ~ EOI }
-
-unicode_escape_sequence = @{ ASCII_HEX_DIGIT{4} }
-
-unicode_letter = _{
-  UPPERCASE_LETTER |
-  LOWERCASE_LETTER |
-  TITLECASE_LETTER |
-  MODIFIER_LETTER |
-  OTHER_LETTER |
-  LETTER_NUMBER
-}
-
-value = _{ null | boolean | string | number | object | array }
diff --git a/third_party/src/lib.rs b/third_party/src/lib.rs
index eaac541..ab8f153 100644
--- a/third_party/src/lib.rs
+++ b/third_party/src/lib.rs
@@ -47,7 +47,7 @@
 //! ";
 //!
 //! assert_eq!(
-//!     json5::from_str(config),
+//!     serde_json5::from_str(config),
 //!     Ok(Config {
 //!         message: "hello world".to_string(),
 //!         n: 42,
@@ -78,7 +78,7 @@
 //! }
 //!
 //! assert_eq!(
-//!     json5::to_string(&vec![
+//!     serde_json5::to_string(&vec![
 //!         Val::Number(42.),
 //!         Val::Bool(true),
 //!         Val::String("hello".to_owned()),
@@ -122,6 +122,6 @@
 mod error;
 mod ser;
 
-pub use crate::de::from_str;
+pub use crate::de::{from_str, from_slice, from_reader};
 pub use crate::error::{Error, Location, Result};
 pub use crate::ser::to_string;
diff --git a/third_party/src/ser.rs b/third_party/src/ser.rs
index 6e271fc..3fa22b2 100644
--- a/third_party/src/ser.rs
+++ b/third_party/src/ser.rs
@@ -364,7 +364,6 @@
             '\n' => vec!['\\', 'n'],
             '\r' => vec!['\\', 'r'],
             '\t' => vec!['\\', 't'],
-            '/' => vec!['\\', '/'],
             '\\' => vec!['\\', '\\'],
             '\u{0008}' => vec!['\\', 'b'],
             '\u{000c}' => vec!['\\', 'f'],
diff --git a/third_party/tests/adapted_from_js_reference.rs b/third_party/tests/adapted_from_js_reference.rs
index 8e2f631..7ac8613 100644
--- a/third_party/tests/adapted_from_js_reference.rs
+++ b/third_party/tests/adapted_from_js_reference.rs
@@ -1,8 +1,6 @@
 use std::collections::HashMap;
 use std::f64;
 
-mod common;
-
 use crate::common::{deserializes_to, deserializes_to_nan_f64};
 
 // The following tests are adapted from https://github.com/json5/json5/blob/d828908384ce8dc40d8dde017ae82afd1b952d79/test/parse.js
diff --git a/third_party/tests/common.rs b/third_party/tests/common.rs
index 9eb1ff2..362a6c2 100644
--- a/third_party/tests/common.rs
+++ b/third_party/tests/common.rs
@@ -1,22 +1,22 @@
-use json5::{Error, Location};
 use matches::assert_matches;
+use serde_json5::{Error, Location};
 
 #[allow(unused)]
 pub fn deserializes_to<'a, T>(s: &'a str, v: T)
 where
     T: ::std::fmt::Debug + ::std::cmp::PartialEq + serde::de::Deserialize<'a>,
 {
-    assert_matches!(json5::from_str::<T>(s), Ok(value) if value == v);
+    assert_matches!(serde_json5::from_str::<T>(s), Ok(value) if value == v);
 }
 
 #[allow(unused)]
 pub fn deserializes_to_nan_f32<'a>(s: &'a str) {
-    assert_matches!(json5::from_str::<f32>(s), Ok(value) if value.is_nan());
+    assert_matches!(serde_json5::from_str::<f32>(s), Ok(value) if value.is_nan());
 }
 
 #[allow(unused)]
 pub fn deserializes_to_nan_f64<'a>(s: &'a str) {
-    assert_matches!(json5::from_str::<f64>(s), Ok(value) if value.is_nan());
+    assert_matches!(serde_json5::from_str::<f64>(s), Ok(value) if value.is_nan());
 }
 
 #[allow(unused)]
@@ -24,7 +24,7 @@
 where
     T: ::std::fmt::Debug + ::std::cmp::PartialEq + serde::de::Deserialize<'a>,
 {
-    assert_matches!(json5::from_str::<T>(s), Err(e) if e == error_expected);
+    assert_matches!(serde_json5::from_str::<T>(s), Err(e) if e == error_expected);
 }
 
 #[allow(unused)]
@@ -32,7 +32,7 @@
 where
     T: ::std::fmt::Debug + ::std::cmp::PartialEq + serde::ser::Serialize,
 {
-    assert_matches!(json5::to_string::<T>(&v), Ok(value) if value == s);
+    assert_matches!(serde_json5::to_string::<T>(&v), Ok(value) if value == s);
 }
 
 #[allow(unused)]
diff --git a/third_party/tests/de.rs b/third_party/tests/de.rs
index 0474d8c..a76fa10 100644
--- a/third_party/tests/de.rs
+++ b/third_party/tests/de.rs
@@ -4,8 +4,6 @@
 use std::collections::HashMap;
 use std::fmt;
 
-mod common;
-
 use crate::common::{
     deserializes_to, deserializes_to_nan_f32, deserializes_to_nan_f64, deserializes_with_error,
     make_error,
@@ -463,11 +461,7 @@
     deserializes_to("[1, 2, 3]", vec![1, 2, 3]);
     deserializes_to(
         "[42, true, 'hello']",
-        vec![
-            Val::Number(42.),
-            Val::Bool(true),
-            Val::String("hello".to_owned()),
-        ],
+        vec![Val::Number(42.), Val::Bool(true), Val::String("hello".to_owned())],
     );
 }
 
@@ -764,3 +758,43 @@
         make_error("invalid type: integer `42`, expected a boolean", 2, 2),
     );
 }
+
+#[test]
+fn test_from_str() {
+    #[derive(Deserialize, PartialEq, Debug)]
+    struct S {
+        a: i32,
+        b: i32,
+        c: i32,
+    }
+
+    let data = "{ a: 5, b: 4, c: 3}";
+    assert_eq!(serde_json5::from_str(data), Ok(S{ a: 5, b:4, c:3}))
+}
+
+#[test]
+fn test_from_slice() {
+    #[derive(Deserialize, PartialEq, Debug)]
+    struct S {
+        a: i32,
+        b: i32,
+        c: i32,
+    }
+
+    let data = "{ a: 5, b: 4, c: 3}".as_bytes();
+    assert_eq!(serde_json5::from_slice(data), Ok(S{ a: 5, b:4, c:3}))
+}
+
+#[test]
+fn test_from_reader() {
+    #[derive(Deserialize, PartialEq, Debug)]
+    struct S {
+        a: i32,
+        b: i32,
+        c: i32,
+    }
+
+    let data = "{ a: 5, b: 4, c: 3}";
+    let mut reader = std::io::Cursor::new(data);
+    assert_eq!(serde_json5::from_reader(&mut reader), Ok(S{ a: 5, b:4, c:3}))
+}
\ No newline at end of file
diff --git a/third_party/tests/json5_dot_org_example.rs b/third_party/tests/json5_dot_org_example.rs
index 8a4390a..7cc6d13 100644
--- a/third_party/tests/json5_dot_org_example.rs
+++ b/third_party/tests/json5_dot_org_example.rs
@@ -2,8 +2,6 @@
 use std::fs::File;
 use std::io::prelude::*;
 
-mod common;
-
 use crate::common::deserializes_to;
 
 #[derive(Deserialize, PartialEq, Debug)]
@@ -24,7 +22,7 @@
 #[test]
 fn serializes_example_from_json5_dot_org() {
     let mut contents = String::new();
-    File::open("tests/assets/json5_dot_org_example.json5")
+    File::open("third_party/tests/assets/json5_dot_org_example.json5")
         .unwrap()
         .read_to_string(&mut contents)
         .unwrap();
diff --git a/third_party/tests/lib.rs b/third_party/tests/lib.rs
new file mode 100644
index 0000000..2578ca4
--- /dev/null
+++ b/third_party/tests/lib.rs
@@ -0,0 +1,5 @@
+mod adapted_from_js_reference;
+mod common;
+mod de;
+mod json5_dot_org_example;
+mod ser;
diff --git a/third_party/tests/ser.rs b/third_party/tests/ser.rs
index 31c5e03..c9632de 100644
--- a/third_party/tests/ser.rs
+++ b/third_party/tests/ser.rs
@@ -2,8 +2,6 @@
 
 use std::collections::HashMap;
 
-mod common;
-
 use crate::common::serializes_to;
 
 #[test]
@@ -163,11 +161,7 @@
 
     serializes_to(vec![1, 2, 3], "[1,2,3]");
     serializes_to(
-        vec![
-            Val::Number(42.),
-            Val::Bool(true),
-            Val::String("hello".to_owned()),
-        ],
+        vec![Val::Number(42.), Val::Bool(true), Val::String("hello".to_owned())],
         "[42,true,\"hello\"]",
     )
 }