├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE-Apache-2.0
├── LICENSE-ISC
├── README.md
├── src
├── de.rs
├── error.rs
├── lib.rs
└── ser.rs
└── tests
├── adapted_from_js_reference.rs
├── assets
└── json5_dot_org_example.json5
├── common
└── mod.rs
├── de.rs
├── json5_dot_org_example.rs
└── ser.rs
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | ci:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os: [ubuntu-latest, windows-latest, macOS-latest]
15 | rust:
16 | - stable
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v1
20 |
21 | - name: Install Rust
22 | uses: actions-rs/toolchain/@v1
23 | with:
24 | profile: minimal
25 | toolchain: ${{ matrix.rust }}
26 | override: true
27 |
28 | - name: Build
29 | uses: actions-rs/cargo@v1
30 | with:
31 | command: build
32 |
33 | - name: Run Tests
34 | uses: actions-rs/cargo@v1
35 | with:
36 | command: test
37 | args: ${{ matrix.features }}
38 |
39 | rustfmt:
40 | name: rustfmt
41 | runs-on: ubuntu-latest
42 | steps:
43 | - name: Checkout repository
44 | uses: actions/checkout@v1
45 |
46 | - name: Install Rust
47 | uses: actions-rs/toolchain@v1
48 | with:
49 | toolchain: stable
50 | override: true
51 | profile: minimal
52 | components: rustfmt
53 |
54 | - name: Check formatting
55 | run: cargo fmt --all -- --check
56 |
57 | clippy:
58 | name: clippy
59 | runs-on: ubuntu-latest
60 | steps:
61 | - name: Checkout repository
62 | uses: actions/checkout@v1
63 |
64 | - name: Install Rust
65 | uses: actions-rs/toolchain@v1
66 | with:
67 | toolchain: stable
68 | override: true
69 | profile: minimal
70 | components: rustfmt
71 |
72 | - name: Check Clippy
73 | run: cargo clippy --all --tests --workspace -- -D warnings
74 |
75 | docs:
76 | name: docs
77 | runs-on: ubuntu-latest
78 | steps:
79 | - name: Checkout repository
80 | uses: actions/checkout@v1
81 |
82 | - name: Install Rust
83 | uses: actions-rs/toolchain@v1
84 | with:
85 | toolchain: stable
86 | override: true
87 | profile: minimal
88 |
89 | - name: Check documentation
90 | env:
91 | RUSTDOCFLAGS: -D warnings
92 | run: cargo doc --no-deps --document-private-items --workspace
93 |
94 | coverage:
95 | name: coverage
96 | runs-on: ubuntu-latest
97 |
98 | steps:
99 | - name: Checkout repository
100 | uses: actions/checkout@v2
101 | with:
102 | fetch-depth: 0
103 |
104 | - name: Install Rust
105 | uses: actions-rs/toolchain@v1
106 | with:
107 | toolchain: nightly
108 | override: true
109 | components: llvm-tools-preview
110 |
111 | - name: Install cargo-llvm-cov
112 | uses: taiki-e/install-action@cargo-llvm-cov
113 |
114 | - name: Generate code coverage
115 | run: cargo llvm-cov --workspace --lcov --output-path lcov.info
116 |
117 | - name: Upload coverage to coveralls
118 | uses: coverallsapp/github-action@master
119 | with:
120 | github-token: ${{ secrets.GITHUB_TOKEN }}
121 | path-to-lcov: ./lcov.info
122 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Cargo.lock
2 | target/
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement (CLA). You (or your employer) retain the copyright to your
10 | contribution; this simply gives us permission to use and redistribute your
11 | contributions as part of the project. Head over to
12 | to see your current agreements on file or
13 | to sign a new one.
14 |
15 | You generally only need to submit a CLA once, so if you've already submitted one
16 | (even if it was for a different project), you probably don't need to do it
17 | again.
18 |
19 | ## Code reviews
20 |
21 | All submissions, including submissions by project members, require review. We
22 | use GitHub pull requests for this purpose. Consult
23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
24 | information on using pull requests.
25 |
26 | ## Community Guidelines
27 |
28 | This project follows
29 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/).
30 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "serde_json5"
3 | version = "0.2.1"
4 | authors = ["Gary Bressler "]
5 | description = "A Serde (de)serializer for JSON5."
6 | license = "Apache-2.0 AND ISC"
7 | repository = "https://github.com/google/serde_json5"
8 | readme = "README.md"
9 | keywords = ["json5", "json", "serde", "serialization"]
10 | edition = "2021"
11 |
12 | [dependencies]
13 | pest = "2.0"
14 | pest_derive = "2.0"
15 | serde = "1.0"
16 |
17 | [dev-dependencies]
18 | matches = "0.1.8"
19 | serde_derive = "1.0"
20 | serde_json = "1.0"
21 |
--------------------------------------------------------------------------------
/LICENSE-Apache-2.0:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/LICENSE-ISC:
--------------------------------------------------------------------------------
1 | Copyright 2018 Callum Oakley
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 | PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSON5
2 |
3 | A Rust library for serializing and deserializing [JSON5][json5] with
4 | [Serde][serde].
5 |
6 | This project is a fork of [callum-oakley/json5-rs][github-json5-rs].
7 |
8 | [json5]: https://json5.org/
9 | [serde]: https://serde.rs/
10 | [github-json5-rs]: https://github.com/callum-oakley/json5-rs
11 |
--------------------------------------------------------------------------------
/src/de.rs:
--------------------------------------------------------------------------------
1 | use pest::iterators::Pair;
2 | use pest::Parser as P;
3 | use pest_derive::Parser;
4 | use serde::de;
5 | use serde::forward_to_deserialize_any;
6 | use std::char;
7 | use std::collections::VecDeque;
8 | use std::f64;
9 | use std::io::Read;
10 |
11 | use crate::error::{self, Error, Result};
12 |
13 | #[derive(Parser)]
14 | #[grammar_inline = r#"
15 | // see https://spec.json5.org/#syntactic-grammar and
16 | // https://spec.json5.org/#lexical-grammar
17 |
18 | COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" | "//" ~ (!line_terminator ~ ANY)* }
19 |
20 | WHITESPACE = _{
21 | "\u{0009}" |
22 | "\u{000B}" |
23 | "\u{000C}" |
24 | "\u{0020}" |
25 | "\u{00A0}" |
26 | "\u{FEFF}" |
27 | SPACE_SEPARATOR |
28 | line_terminator
29 | }
30 |
31 | array = { "[" ~ "]" | "[" ~ value ~ ("," ~ value)* ~ ","? ~ "]" }
32 |
33 | boolean = @{ "true" | "false" }
34 |
35 | char_escape_sequence = @{ single_escape_char | non_escape_char }
36 |
37 | char_literal = @{ !("\\" | line_terminator) ~ ANY }
38 |
39 | decimal_integer_literal = _{ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
40 |
41 | decimal_literal = _{
42 | decimal_integer_literal ~ "." ~ ASCII_DIGIT* ~ exponent_part? |
43 | "." ~ ASCII_DIGIT+~ exponent_part? |
44 | decimal_integer_literal ~ exponent_part?
45 | }
46 |
47 | double_quote_char = _{
48 | "\\" ~ escape_sequence |
49 | line_continuation |
50 | !"\"" ~ char_literal
51 | }
52 |
53 | escape_char = _{ single_escape_char | ASCII_DIGIT | "x" | "u" }
54 |
55 | escape_sequence = _{
56 | char_escape_sequence |
57 | nul_escape_sequence |
58 | "x" ~ hex_escape_sequence |
59 | "u" ~ unicode_escape_sequence
60 | }
61 |
62 | exponent_part = _{ ^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+ }
63 |
64 | hex_escape_sequence = @{ ASCII_HEX_DIGIT{2} }
65 |
66 | hex_integer_literal = _{ ^"0x" ~ ASCII_HEX_DIGIT+ }
67 |
68 | identifier = ${ identifier_start ~ identifier_part* }
69 |
70 | identifier_part = _{
71 | identifier_start |
72 | &(
73 | NONSPACING_MARK |
74 | DIACRITIC | // not sure about this, spec says "Combining spacing mark (Mc)"
75 | DECIMAL_NUMBER |
76 | CONNECTOR_PUNCTUATION |
77 | "\u{200C}" |
78 | "\u{200D}"
79 | ) ~ char_literal
80 | }
81 |
82 | identifier_start = _{
83 | &(unicode_letter | "$" | "_") ~ char_literal |
84 | "\\u" ~ unicode_escape_sequence
85 | }
86 |
87 | key = _{ identifier | string }
88 |
89 | line_continuation = _{ "\\" ~ line_terminator_sequence }
90 |
91 | line_terminator = _{ "\u{000A}" | "\u{000D}" | "\u{2028}" | "\u{2029}" }
92 |
93 | line_terminator_sequence = _{ "\u{000D}" ~ "\u{000A}" | line_terminator }
94 |
95 | non_escape_char = _{ !(escape_char | line_terminator) ~ ANY }
96 |
97 | nul_escape_sequence = @{ "0" }
98 |
99 | null = @{ "null" }
100 |
101 | number = @{ ("+" | "-")? ~ numeric_literal }
102 |
103 | numeric_literal = _{
104 | hex_integer_literal |
105 | decimal_literal |
106 | "Infinity" |
107 | "NaN"
108 | }
109 |
110 | object = { "{" ~ "}" | "{" ~ pair ~ ("," ~ pair)* ~ ","? ~ "}" }
111 |
112 | pair = _{ key ~ ":" ~ value }
113 |
114 | single_escape_char = _{ "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" }
115 |
116 | single_quote_char = _{
117 | "\\" ~ escape_sequence |
118 | line_continuation |
119 | !"'" ~ char_literal
120 | }
121 |
122 | string = ${ "\"" ~ double_quote_char* ~ "\"" | "'" ~ single_quote_char* ~ "'" }
123 |
124 | text = _{ SOI ~ value ~ EOI }
125 |
126 | unicode_escape_sequence = @{ ASCII_HEX_DIGIT{4} }
127 |
128 | unicode_letter = _{
129 | UPPERCASE_LETTER |
130 | LOWERCASE_LETTER |
131 | TITLECASE_LETTER |
132 | MODIFIER_LETTER |
133 | OTHER_LETTER |
134 | LETTER_NUMBER
135 | }
136 |
137 | value = _{ null | boolean | string | number | object | array }
138 | "#]
139 | struct Parser;
140 |
141 | /// Deserialize an instance of type `T` from a string of JSON5 text. Can fail if the input is
142 | /// invalid JSON5, or doesn’t match the structure of the target type.
143 | pub fn from_str<'a, T>(s: &'a str) -> Result
144 | where
145 | T: de::Deserialize<'a>,
146 | {
147 | let mut deserializer = Deserializer::from_str(s)?;
148 | T::deserialize(&mut deserializer)
149 | }
150 |
151 | /// Deserialize an instance of type `T` from a slice of JSON5 text. Can fail if the input is
152 | /// invalid JSON5, or doesn’t match the structure of the target type.
153 | pub fn from_slice<'a, T>(s: &'a [u8]) -> Result
154 | where
155 | T: de::Deserialize<'a>,
156 | {
157 | let valid_utf8 = std::str::from_utf8(s)?;
158 | let mut deserializer = Deserializer::from_str(valid_utf8)?;
159 | T::deserialize(&mut deserializer)
160 | }
161 |
162 | /// Deserialize an instance of type `T` from any implementation of Read. Can fail if the input is
163 | /// invalid JSON5, or doesn’t match the structure of the target type.
164 | pub fn from_reader(mut reader: R) -> Result
165 | where
166 | T: serde::de::DeserializeOwned,
167 | R: Read,
168 | {
169 | let mut data = String::default();
170 | reader.read_to_string(&mut data)?;
171 | from_str(&data)
172 | }
173 |
174 | /// A Deserializes JSON data into a Rust value.
175 | pub struct Deserializer<'de> {
176 | pair: Option>,
177 | }
178 |
179 | impl<'de> Deserializer<'de> {
180 | /// Creates a JSON5 deserializer from a `&str`. This parses the input at construction time, so
181 | /// can fail if the input is not valid JSON5.
182 | #[allow(clippy::should_implement_trait)]
183 | pub fn from_str(input: &'de str) -> Result {
184 | let pair = Parser::parse(Rule::text, input)?.next().unwrap();
185 | Ok(Deserializer::from_pair(pair))
186 | }
187 |
188 | fn from_pair(pair: Pair<'de, Rule>) -> Self {
189 | Deserializer { pair: Some(pair) }
190 | }
191 | }
192 |
193 | impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> {
194 | type Error = Error;
195 |
196 | fn deserialize_any(self, visitor: V) -> Result
197 | where
198 | V: de::Visitor<'de>,
199 | {
200 | let pair = self.pair.take().unwrap();
201 | let span = pair.as_span();
202 | let mut res = (move || match pair.as_rule() {
203 | Rule::null => visitor.visit_unit(),
204 | Rule::boolean => visitor.visit_bool(parse_bool(&pair)),
205 | Rule::string | Rule::identifier => visitor.visit_string(parse_string(pair)?),
206 | Rule::number => {
207 | if is_int(pair.as_str()) {
208 | visitor.visit_i64(parse_integer(&pair)?)
209 | } else {
210 | visitor.visit_f64(parse_number(&pair)?)
211 | }
212 | }
213 | Rule::array => visitor.visit_seq(Seq::new(pair)),
214 | Rule::object => visitor.visit_map(Map::new(pair)),
215 | _ => unreachable!(),
216 | })();
217 | error::set_location(&mut res, &span);
218 | res
219 | }
220 |
221 | fn deserialize_enum(
222 | self,
223 | _name: &'static str,
224 | _variants: &'static [&'static str],
225 | visitor: V,
226 | ) -> Result
227 | where
228 | V: de::Visitor<'de>,
229 | {
230 | let pair = self.pair.take().unwrap();
231 | let span = pair.as_span();
232 | let mut res = visitor.visit_enum(Enum { pair });
233 | error::set_location(&mut res, &span);
234 | res
235 | }
236 |
237 | // The below will get us the right types, but won't necessarily give
238 | // meaningful results if the source is out of the range of the target type.
239 | fn deserialize_i8(self, visitor: V) -> Result
240 | where
241 | V: de::Visitor<'de>,
242 | {
243 | let pair = self.pair.take().unwrap();
244 | let span = pair.as_span();
245 | let mut res = (move || visitor.visit_i8(parse_number(&pair)? as i8))();
246 | error::set_location(&mut res, &span);
247 | res
248 | }
249 |
250 | fn deserialize_i16(self, visitor: V) -> Result
251 | where
252 | V: de::Visitor<'de>,
253 | {
254 | let pair = self.pair.take().unwrap();
255 | let span = pair.as_span();
256 | let mut res = (move || visitor.visit_i16(parse_number(&pair)? as i16))();
257 | error::set_location(&mut res, &span);
258 | res
259 | }
260 |
261 | fn deserialize_i32(self, visitor: V) -> Result
262 | where
263 | V: de::Visitor<'de>,
264 | {
265 | let pair = self.pair.take().unwrap();
266 | let span = pair.as_span();
267 | let mut res = (move || visitor.visit_i32(parse_number(&pair)? as i32))();
268 | error::set_location(&mut res, &span);
269 | res
270 | }
271 |
272 | fn deserialize_i64(self, visitor: V) -> Result
273 | where
274 | V: de::Visitor<'de>,
275 | {
276 | let pair = self.pair.take().unwrap();
277 | let span = pair.as_span();
278 | let mut res = (move || visitor.visit_i64(parse_number(&pair)? as i64))();
279 | error::set_location(&mut res, &span);
280 | res
281 | }
282 |
283 | fn deserialize_i128(self, visitor: V) -> Result
284 | where
285 | V: de::Visitor<'de>,
286 | {
287 | let pair = self.pair.take().unwrap();
288 | let span = pair.as_span();
289 | let mut res = (move || visitor.visit_i128(parse_number(&pair)? as i128))();
290 | error::set_location(&mut res, &span);
291 | res
292 | }
293 |
294 | fn deserialize_u8(self, visitor: V) -> Result
295 | where
296 | V: de::Visitor<'de>,
297 | {
298 | let pair = self.pair.take().unwrap();
299 | let span = pair.as_span();
300 | let mut res = (move || visitor.visit_u8(parse_number(&pair)? as u8))();
301 | error::set_location(&mut res, &span);
302 | res
303 | }
304 |
305 | fn deserialize_u16(self, visitor: V) -> Result
306 | where
307 | V: de::Visitor<'de>,
308 | {
309 | let pair = self.pair.take().unwrap();
310 | let span = pair.as_span();
311 | let mut res = (move || visitor.visit_u16(parse_number(&pair)? as u16))();
312 | error::set_location(&mut res, &span);
313 | res
314 | }
315 |
316 | fn deserialize_u32(self, visitor: V) -> Result
317 | where
318 | V: de::Visitor<'de>,
319 | {
320 | let pair = self.pair.take().unwrap();
321 | let span = pair.as_span();
322 | let mut res = (move || visitor.visit_u32(parse_number(&pair)? as u32))();
323 | error::set_location(&mut res, &span);
324 | res
325 | }
326 |
327 | fn deserialize_u64(self, visitor: V) -> Result
328 | where
329 | V: de::Visitor<'de>,
330 | {
331 | let pair = self.pair.take().unwrap();
332 | let span = pair.as_span();
333 | let mut res = (move || visitor.visit_u64(parse_number(&pair)? as u64))();
334 | error::set_location(&mut res, &span);
335 | res
336 | }
337 |
338 | fn deserialize_u128(self, visitor: V) -> Result
339 | where
340 | V: de::Visitor<'de>,
341 | {
342 | let pair = self.pair.take().unwrap();
343 | let span = pair.as_span();
344 | let mut res = (move || visitor.visit_u128(parse_number(&pair)? as u128))();
345 | error::set_location(&mut res, &span);
346 | res
347 | }
348 |
349 | fn deserialize_f32(self, visitor: V) -> Result
350 | where
351 | V: de::Visitor<'de>,
352 | {
353 | let pair = self.pair.take().unwrap();
354 | let span = pair.as_span();
355 | let mut res = (move || visitor.visit_f32(parse_number(&pair)? as f32))();
356 | error::set_location(&mut res, &span);
357 | res
358 | }
359 |
360 | fn deserialize_f64(self, visitor: V) -> Result
361 | where
362 | V: de::Visitor<'de>,
363 | {
364 | let pair = self.pair.take().unwrap();
365 | let span = pair.as_span();
366 | let mut res = (move || visitor.visit_f64(parse_number(&pair)?))();
367 | error::set_location(&mut res, &span);
368 | res
369 | }
370 |
371 | fn deserialize_option(self, visitor: V) -> Result
372 | where
373 | V: de::Visitor<'de>,
374 | {
375 | let pair = self.pair.take().unwrap();
376 | let span = pair.as_span();
377 | let mut res = match pair.as_rule() {
378 | Rule::null => visitor.visit_none(),
379 | _ => visitor.visit_some(&mut Deserializer::from_pair(pair)),
380 | };
381 | error::set_location(&mut res, &span);
382 | res
383 | }
384 |
385 | fn deserialize_newtype_struct(self, _name: &str, visitor: V) -> Result
386 | where
387 | V: de::Visitor<'de>,
388 | {
389 | let span = self.pair.as_ref().unwrap().as_span();
390 | let mut res = visitor.visit_newtype_struct(self);
391 | error::set_location(&mut res, &span);
392 | res
393 | }
394 |
395 | forward_to_deserialize_any! {
396 | bool char str string bytes byte_buf unit unit_struct seq
397 | tuple tuple_struct map struct identifier ignored_any
398 | }
399 | }
400 |
401 | fn parse_bool(pair: &Pair<'_, Rule>) -> bool {
402 | match pair.as_str() {
403 | "true" => true,
404 | "false" => false,
405 | _ => unreachable!(),
406 | }
407 | }
408 |
409 | fn parse_string(pair: Pair<'_, Rule>) -> Result {
410 | let span = pair.as_span();
411 | let mut res = pair
412 | .into_inner()
413 | .map(|component| match component.as_rule() {
414 | Rule::char_literal => Ok(String::from(component.as_str())),
415 | Rule::char_escape_sequence => Ok(parse_char_escape_sequence(&component)),
416 | Rule::nul_escape_sequence => Ok(String::from("\u{0000}")),
417 | Rule::hex_escape_sequence | Rule::unicode_escape_sequence => {
418 | let hex_escape = parse_hex(component.as_str())?;
419 | match char::from_u32(hex_escape) {
420 | Some(s) => Ok(s.to_string()),
421 | None => Err(de::Error::custom("error parsing hex prefix")),
422 | }
423 | }
424 | _ => unreachable!(),
425 | })
426 | .collect();
427 | error::set_location(&mut res, &span);
428 | res
429 | }
430 |
431 | fn parse_char_escape_sequence(pair: &Pair<'_, Rule>) -> String {
432 | String::from(match pair.as_str() {
433 | "b" => "\u{0008}",
434 | "f" => "\u{000C}",
435 | "n" => "\n",
436 | "r" => "\r",
437 | "t" => "\t",
438 | "v" => "\u{000B}",
439 | c => c,
440 | })
441 | }
442 |
443 | fn parse_number(pair: &Pair<'_, Rule>) -> Result {
444 | match pair.as_str() {
445 | "Infinity" => Ok(f64::INFINITY),
446 | "-Infinity" => Ok(f64::NEG_INFINITY),
447 | "NaN" | "-NaN" => Ok(f64::NAN),
448 | s if is_hex_literal(s) => parse_hex(&s[2..]).map(f64::from),
449 | s => {
450 | if let Ok(r) = s.parse::() {
451 | if r.is_finite() {
452 | Ok(r)
453 | } else {
454 | Err(de::Error::custom("error parsing number: too large"))
455 | }
456 | } else {
457 | Err(de::Error::custom("error parsing number"))
458 | }
459 | }
460 | }
461 | }
462 |
463 | fn parse_integer(pair: &Pair<'_, Rule>) -> Result {
464 | match pair.as_str() {
465 | s if is_hex_literal(s) => Ok(parse_hex(&s[2..])? as i64),
466 | s => s
467 | .parse()
468 | .map_err(|_| de::Error::custom("error parsing integer")),
469 | }
470 | }
471 |
472 | fn is_int(s: &str) -> bool {
473 | !s.contains('.')
474 | && (is_hex_literal(s)
475 | || (!s.contains('e')
476 | && !s.contains('E')
477 | && !s.contains("Infinity")
478 | && !s.contains("NaN")))
479 | }
480 |
481 | fn parse_hex(s: &str) -> Result {
482 | u32::from_str_radix(s, 16).map_err(|_| de::Error::custom("error parsing hex"))
483 | }
484 |
485 | fn is_hex_literal(s: &str) -> bool {
486 | s.len() > 2 && (&s[..2] == "0x" || &s[..2] == "0X")
487 | }
488 |
489 | struct Seq<'de> {
490 | pairs: VecDeque>,
491 | }
492 |
493 | impl<'de> Seq<'de> {
494 | pub fn new(pair: Pair<'de, Rule>) -> Self {
495 | Self {
496 | pairs: pair.into_inner().collect(),
497 | }
498 | }
499 | }
500 |
501 | impl<'de> de::SeqAccess<'de> for Seq<'de> {
502 | type Error = Error;
503 |
504 | fn size_hint(&self) -> Option {
505 | Some(self.pairs.len())
506 | }
507 |
508 | fn next_element_seed(&mut self, seed: T) -> Result