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 Parentheses, 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 InlineRust(InlineRust),
63 } else "Expected JSON5 value."; struct Array: PeekFrom, PopFrom (SquareBrackets<Delimited<Value, Comma>>);
66
67 struct Object: PeekFrom, PopFrom (Braces<Delimited<Property, Comma>>);
68
69 struct Property: PopFrom {
70 key: Key,
71 colon: Colon,
72 value: Value,
73 }
74
75 enum Key: PopFrom {
76 Identifier(Identifier),
77 String(String),
78 } else "Expected key (plain identifier or string literal).";
79
80 enum Number: PeekFrom, PopFrom {
81 NaN(NaN),
82 NotNaN(NotNaN),
83 } else "Expected Number."; struct NotNaN: PopFrom {
86 sign: Option<Sign>,
87 amount: Amount,
88 }
89
90 enum Sign: PeekFrom, PopFrom {
91 Plus(Plus),
92 Minus(Minus),
93 } else "Expected Sign."; enum Amount: PeekFrom, PopFrom {
96 Finite(NumberLiteral), Infinity(Infinity),
98 } else "Expected number literal or `infinity`.";
99
100 struct InlineRust: PeekFrom, PopFrom (Parentheses);
101}
102
103impl Value {
104 fn span(&self) -> Span {
106 match self {
107 Value::String(s) => s.0.span(),
108 Value::Number(number) => match number {
109 Number::NaN(nan) => nan.0.span(),
110 Number::NotNaN(not_nan) => not_nan.amount.span(),
111 },
112 Value::Object(object) => object.0.span.join(),
113 Value::Array(array) => array.0.span.join(),
114 Value::True(t) => t.0.span(),
115 Value::False(f) => f.0.span(),
116 Value::Null(n) => n.0.span(),
117 Value::InlineRust(inline_rust) => inline_rust.0.span.join(),
118 }
119 }
120}
121
122impl PeekFrom for NotNaN {
127 fn peek_from(input: &Input) -> bool {
128 Sign::peek_from(input) || Amount::peek_from(input)
129 }
130}
131
132impl IntoTokens for Object {
135 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
136 quote_into_mixed_site!(self.0.span.join(), root, tokens, [
137 {#root}::json::JsonValue::Object({
138 let mut object = {#root}::json::object::Object::with_capacity(
139 {#paste Literal::usize_unsuffixed(self.0.contents.0.len()) }
141 );
142 {#for (property, comma) in self.0.contents.0,
143 {#let Property { key, colon, value } = property;}
144 {#located_at value.span(),
145 object.insert(
146 {#paste key }
147 {#located_at colon.0.span(), , }
148 {#paste value }
149 )
150 }
151 {#located_at comma.map(|comma| comma.0.span()).unwrap_or(self.0.span.close()), ; }
152 }
153 object
154 })
155 ])
156 }
157}
158
159impl IntoTokens for Key {
160 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
161 match self {
162 Key::Identifier(identifier) => {
163 let mut s = Literal::string(&identifier.0.to_string());
164 s.set_span(identifier.0.span());
165 s.into_tokens(root, tokens)
166 }
167 Key::String(s) => s.0.into_tokens(root, tokens),
168 }
169 }
170}
171
172impl IntoTokens for Array {
173 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
174 quote_into_mixed_site!(self.0.span.join(), root, tokens, [
175 {#root}::json::JsonValue::Array({
176 let mut vec = {#root}::std::vec::Vec::with_capacity(
177 {#paste Literal::usize_unsuffixed(self.0.contents.0.len()) }
179 );
180 {#for (item, comma) in self.0.contents.0,
181 {#located_at item.span(),
182 vec.push({#paste item })
183 }
184 {#located_at comma.map(|comma| comma.0.span()).unwrap_or(self.0.span.close()), ; }
185 }
186 vec
187 })
188 ])
189 }
190}
191
192impl IntoTokens for Number {
193 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
194 let span = match &self {
195 Number::NaN(nan) => nan.0.span(),
196 Number::NotNaN(NotNaN {
197 sign: Some(sign),
198 amount,
199 }) => sign.span().join(amount.span()).unwrap_or(amount.span()),
200 Number::NotNaN(NotNaN { sign: None, amount }) => amount.span(),
201 };
202 quote_into_mixed_site!(span, root, tokens, [
203 {#root}::json::JsonValue::Number(
204 {#match self,
205 Self::NaN(_) => { {#root}::json::number::NAN }
206 Self::NotNaN(NotNaN { sign, amount }) => {
207 {#root}::core::convert::From::from(
208 {#match (sign, amount),
209 (Some(Sign::Minus(_)), Amount::Infinity(_)) => {
210 {#root}::core::f64::NEG_INFINITY
211 }
212 (_, Amount::Infinity(_)) => {
213 {#root}::core::f64::INFINITY
214 }
215 (Some(Sign::Minus(minus)), amount) => {
216 {#paste minus } {#paste amount }
217 }
218 (_, amount) => { {#paste amount } }
219 }
220 )
221 }
222 }
223 )
224 ])
225 }
226}
227
228impl SimpleSpanned for Sign {
229 fn span(&self) -> Span {
230 match self {
231 Sign::Plus(plus) => &plus.0,
232 Sign::Minus(minus) => &minus.0,
233 }
234 .span()
235 }
236
237 fn set_span(&mut self, span: Span) {
238 match self {
239 Sign::Plus(plus) => &mut plus.0,
240 Sign::Minus(minus) => &mut minus.0,
241 }
242 .set_span(span)
243 }
244}
245
246impl Amount {
247 fn span(&self) -> Span {
248 match self {
249 Amount::Finite(NumberLiteral(dot, literal)) => dot
250 .as_ref()
251 .and_then(|dot| dot.0.span().join(literal.span()))
252 .unwrap_or(literal.span()),
253 Amount::Infinity(infinity) => infinity.0.span(),
254 }
255 }
256}
257
258impl IntoTokens for Amount {
259 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
260 let Self::Finite(finite) = self else {
261 unreachable!("Handled by Number.")
262 };
263 finite.into_tokens(root, tokens)
264 }
265}
266
267impl IntoTokens for True {
268 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
269 quote_into_mixed_site!(self.0.span(), root, tokens, [
270 {#root}::json::JsonValue::Boolean(true)
271 ]);
272 }
273}
274
275impl IntoTokens for False {
276 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
277 quote_into_mixed_site!(self.0.span(), root, tokens, [
278 {#root}::json::JsonValue::Boolean(false)
279 ]);
280 }
281}
282
283impl IntoTokens for Null {
284 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
285 quote_into_mixed_site!(self.0.span(), root, tokens, [
286 {#root}::json::JsonValue::Null
287 ]);
288 }
289}
290
291impl IntoTokens for String {
292 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
293 quote_into_mixed_site!(self.0.span(), root, tokens, [
294 {#root}::json::JsonValue::String(
295 {#paste self.0 }.to_string()
296 )
297 ]);
298 }
299}
300
301impl IntoTokens for InlineRust {
302 fn into_tokens(self, root: &TokenStream, tokens: &mut impl Extend<TokenTree>) {
303 self.0.contents.into_tokens(root, tokens)
305 }
306}
307
308fn hook_panics() {
310 std::panic::set_hook(Box::new(|panic_info| {
311 let location = panic_info.location();
312
313 let payload = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
314 s
315 } else if let Some(s) = panic_info.payload().downcast_ref::<::std::string::String>() {
316 s.as_str()
317 } else {
318 "(unknown panic type)"
319 };
320 eprintln!(
321 "proc macro panic at {}:{}\n\n{}",
322 location.map(|l| l.file()).unwrap_or("None"),
323 location
324 .map(|l| l.line().to_string())
325 .unwrap_or_else(|| "None".to_string()),
326 payload
327 );
328 }))
329}