1use loess::{
2 Errors, Input, IntoTokens, PeekFrom, SimpleSpanned, grammar, parse_all, parse_once,
3 quote_into_mixed_site,
4};
5use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
6
7mod tokens;
8use tokens::{
9 Braces, Colon, Comma, Delimited, False, Identifier, Infinity, Minus, NaN, Null, NumberLiteral,
10 Plus, SquareBrackets, String, True,
11};
12
13#[proc_macro]
14pub fn json5(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 hook_panics();
16 json5_(input.into()).into()
17}
18
19fn json5_(input: TokenStream) -> TokenStream {
21 let mut input = Input {
22 tokens: input.into_iter().collect(),
23 end: Span::call_site(), };
25 let mut errors = Errors::new();
26
27 let Ok(SquareBrackets { contents: root, .. }) = parse_once(&mut input, &mut errors) else {
28 return errors.collect_tokens(&TokenStream::new());
29 };
30
31 let value = parse_all::<Value>(&mut input, &mut errors).next();
33
34 let mut output = errors.collect_tokens(&root);
37
38 if let Some(value) = value {
39 value.into_tokens(&root, &mut output)
40 } else {
41 quote_into_mixed_site!(Span::mixed_site(), &root, &mut output, [
42 {#error "Expected JSON5 value."}
43 {#root}::json::JsonValue::Null
44 ])
45 }
46
47 TokenTree::Group(Group::new(Delimiter::Brace, output)).into()
50}
51
52grammar! {
54 enum Value: PopFrom, IntoTokens {
55 String(String),
56 Number(Number),
57 Object(Object),
58 Array(Array),
59 True(True),
60 False(False),
61 Null(Null),
62 } else "Expected JSON5 value."; struct Array: PeekFrom, PopFrom (SquareBrackets<Delimited<Value, Comma>>);
65
66 struct Object: PeekFrom, PopFrom (Braces<Delimited<Property, Comma>>);
67
68 struct Property: PopFrom {
69 key: Key,
70 colon: Colon,
71 value: Value,
72 }
73
74 enum Key: PopFrom {
75 Identifier(Identifier),
76 String(String),
77 } else "Expected key (plain identifier or string literal).";
78
79 enum Number: PeekFrom, PopFrom {
80 NaN(NaN),
81 NotNaN(NotNaN),
82 } else "Expected Number."; struct NotNaN: PopFrom {
85 sign: Option<Sign>,
86 amount: Amount,
87 }
88
89 enum Sign: PeekFrom, PopFrom {
90 Plus(Plus),
91 Minus(Minus),
92 } else "Expected Sign."; enum Amount: PeekFrom, PopFrom {
95 Finite(NumberLiteral), Infinity(Infinity),
97 } else "Expected number literal or `infinity`.";
98}
99
100impl PeekFrom for NotNaN {
105 fn peek_from(input: &Input) -> bool {
106 Sign::peek_from(input) || Amount::peek_from(input)
107 }
108}
109
110impl IntoTokens for Object {
113 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
114 quote_into_mixed_site!(self.0.span.join(), root, tokens, [
115 {#root}::json::JsonValue::Object({
116 let mut object = {#root}::json::object::Object::with_capacity(
117 {#paste Literal::usize_unsuffixed(self.0.contents.0.len()) }
119 );
120 {#for (property, comma) in self.0.contents.0,
121 {#let Property { key, colon, value } = property;}
122 object.insert(
123 {#paste key }
124 {#located_at colon.0.span(), , }
125 {#paste value }
126 )
127 {#located_at comma.map(|comma| comma.0.span()).unwrap_or(self.0.span.close()), ; }
128 }
129 object
130 })
131 ])
132 }
133}
134
135impl IntoTokens for Key {
136 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
137 match self {
138 Key::Identifier(identifier) => {
139 let mut s = Literal::string(&identifier.0.to_string());
140 s.set_span(identifier.0.span());
141 s.into_tokens(root, tokens)
142 }
143 Key::String(s) => s.0.into_tokens(root, tokens),
144 }
145 }
146}
147
148impl IntoTokens for Array {
149 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
150 quote_into_mixed_site!(self.0.span.join(), root, tokens, [
151 {#root}::json::JsonValue::Array({
152 let mut vec = {#root}::std::vec::Vec::with_capacity(
153 {#paste Literal::usize_unsuffixed(self.0.contents.0.len()) }
155 );
156 {#for (item, comma) in self.0.contents.0,
157 vec.push({#paste item })
158 {#located_at comma.map(|comma| comma.0.span()).unwrap_or(self.0.span.close()), ; }
159 }
160 vec
161 })
162 ])
163 }
164}
165
166impl IntoTokens for Number {
167 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
168 let span = match &self {
169 Number::NaN(nan) => nan.0.span(),
170 Number::NotNaN(NotNaN {
171 sign: Some(sign),
172 amount,
173 }) => sign.span().join(amount.span()).unwrap_or(amount.span()),
174 Number::NotNaN(NotNaN { sign: None, amount }) => amount.span(),
175 };
176 quote_into_mixed_site!(span, root, tokens, [
177 {#root}::json::JsonValue::Number(
178 {#match self,
179 Self::NaN(_) => { {#root}::json::number::NAN }
180 Self::NotNaN(NotNaN { sign, amount }) => {
181 {#root}::core::convert::From::from(
182 {#match (sign, amount),
183 (Some(Sign::Minus(_)), Amount::Infinity(_)) => {
184 {#root}::core::f64::NEG_INFINITY
185 }
186 (_, Amount::Infinity(_)) => {
187 {#root}::core::f64::INFINITY
188 }
189 (Some(Sign::Minus(minus)), amount) => {
190 {#paste minus } {#paste amount }
191 }
192 (_, amount) => { {#paste amount } }
193 }
194 )
195 }
196 }
197 )
198 ])
199 }
200}
201
202impl SimpleSpanned for Sign {
203 fn span(&self) -> Span {
204 match self {
205 Sign::Plus(plus) => &plus.0,
206 Sign::Minus(minus) => &minus.0,
207 }
208 .span()
209 }
210
211 fn set_span(&mut self, span: Span) {
212 match self {
213 Sign::Plus(plus) => &mut plus.0,
214 Sign::Minus(minus) => &mut minus.0,
215 }
216 .set_span(span)
217 }
218}
219
220impl Amount {
221 fn span(&self) -> Span {
222 match self {
223 Amount::Finite(NumberLiteral(dot, literal)) => dot
224 .as_ref()
225 .and_then(|dot| dot.0.span().join(literal.span()))
226 .unwrap_or(literal.span()),
227 Amount::Infinity(infinity) => infinity.0.span(),
228 }
229 }
230}
231
232impl IntoTokens for Amount {
233 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
234 let Self::Finite(finite) = self else {
235 unreachable!("Handled by Number.")
236 };
237 finite.into_tokens(root, tokens)
238 }
239}
240
241impl IntoTokens for True {
242 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
243 quote_into_mixed_site!(self.0.span(), root, tokens, [
244 {#root}::json::JsonValue::Boolean(true)
245 ]);
246 }
247}
248
249impl IntoTokens for False {
250 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
251 quote_into_mixed_site!(self.0.span(), root, tokens, [
252 {#root}::json::JsonValue::Boolean(false)
253 ]);
254 }
255}
256
257impl IntoTokens for Null {
258 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
259 quote_into_mixed_site!(self.0.span(), root, tokens, [
260 {#root}::json::JsonValue::Null
261 ]);
262 }
263}
264
265impl IntoTokens for String {
266 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
267 quote_into_mixed_site!(self.0.span(), root, tokens, [
268 {#root}::json::JsonValue::String(
269 {#paste self.0 }.to_string()
270 )
271 ]);
272 }
273}
274
275fn hook_panics() {
277 std::panic::set_hook(Box::new(|panic_info| {
278 let location = panic_info.location();
279
280 let payload = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
281 s
282 } else if let Some(s) = panic_info.payload().downcast_ref::<::std::string::String>() {
283 s.as_str()
284 } else {
285 "(unknown panic type)"
286 };
287 eprintln!(
288 "proc macro panic at {}:{}\n\n{}",
289 location.map(|l| l.file()).unwrap_or("None"),
290 location
291 .map(|l| l.line().to_string())
292 .unwrap_or_else(|| "None".to_string()),
293 payload
294 );
295 }))
296}