1use std::fmt::{self, Display, Formatter};
5
6use derive_more::Display;
7use macro_rules_attribute::{apply, attribute_alias};
8use nom::branch::*;
9use nom::bytes::complete::*;
10use nom::character::complete::*;
11use nom::combinator::*;
12use nom::error::{context, VerboseError};
13use nom::multi::*;
14use nom::sequence::*;
15use nom::{Finish, Parser};
16#[cfg(test)]
17use serde::Serialize;
18use unicode_ident::{is_xid_continue, is_xid_start};
19pub use value::{Char, Int, List, Map, MapItem, NamedField, Str, Struct, Tuple, Value};
20
21type Error<'a> = VerboseError<&'a str>;
22type IResult<'a, T> = nom::IResult<&'a str, T, Error<'a>>;
23
24mod value;
25
26attribute_alias! {
27 #[apply(ast)] =
28 #[cfg_attr(test, derive(Serialize))]
29 #[derive(Debug, PartialEq)];
30}
31
32#[apply(ast)]
33pub struct File<'s> {
34 pub extentions: Vec<WsLead<'s, Attribute<'s>>>,
35 pub value: WsLead<'s, Value<'s>>,
36 pub trailing_ws: Whitespace<'s>,
37}
38
39impl Display for File<'_> {
40 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
41 self.extentions.iter().try_for_each(|e| write!(f, "{e}"))?;
42 write!(f, "{}{}", self.value, self.trailing_ws)
43 }
44}
45
46impl<'s> TryFrom<&'s str> for File<'s> {
47 type Error = String;
48
49 fn try_from(s: &'s str) -> Result<Self, Self::Error> {
50 match file(s).finish() {
51 Ok(("", file)) => Ok(file),
52 Ok((rem, _)) => Err(format!("unexpected: {rem}")),
53 Err(e) => Err(e.to_string()),
54 }
55 }
56}
57
58#[apply(ast)]
59#[derive(Display)]
60#[display(fmt = "#{after_pound}!\
61 {after_exclamation}[{after_bracket}enable{after_enable}({extentions}){after_paren}]")]
62pub struct Attribute<'s> {
63 pub after_pound: Whitespace<'s>,
64 pub after_exclamation: Whitespace<'s>,
65 pub after_bracket: Whitespace<'s>,
66 pub after_enable: Whitespace<'s>,
67 pub extentions: Separated<'s, &'s str>,
68 pub after_paren: Whitespace<'s>,
69}
70
71#[apply(ast)]
72pub struct WsLead<'s, T> {
73 pub leading: Whitespace<'s>,
74 pub content: T,
75}
76impl<T: Display> Display for WsLead<'_, T> {
77 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78 write!(f, "{}{}", self.leading, self.content)
79 }
80}
81fn ws_lead<'a, T>(
82 t: impl Parser<&'a str, T, Error<'a>>,
83) -> impl Parser<&'a str, WsLead<'a, T>, Error<'a>> {
84 map(pair(ws, t), |(leading, content)| WsLead {
85 leading,
86 content,
87 })
88}
89
90#[apply(ast)]
91#[derive(Default)]
92pub struct WsFollowed<'s, T> {
93 pub content: T,
94 pub following: Whitespace<'s>,
95}
96impl<T: Display> Display for WsFollowed<'_, T> {
97 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98 write!(f, "{}{}", self.content, self.following)
99 }
100}
101fn ws_followed<'a, T>(
102 t: impl Parser<&'a str, T, Error<'a>>,
103) -> impl Parser<&'a str, WsFollowed<'a, T>, Error<'a>> {
104 map(pair(t, ws), |(content, following)| WsFollowed {
105 content,
106 following,
107 })
108}
109
110#[apply(ast)]
111#[derive(Default)]
112pub struct WsWrapped<'s, T> {
113 pub leading: Whitespace<'s>,
114 pub content: T,
115 pub following: Whitespace<'s>,
116}
117impl<T: Display> Display for WsWrapped<'_, T> {
118 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119 write!(f, "{}{}{}", self.leading, self.content, self.following)
120 }
121}
122fn ws_wrapped<'a, T>(
123 t: impl Parser<&'a str, T, Error<'a>>,
124) -> impl Parser<&'a str, WsWrapped<'a, T>, Error<'a>> {
125 map(tuple((ws, t, ws)), |(leading, content, following)| {
126 WsWrapped {
127 leading,
128 content,
129 following,
130 }
131 })
132}
133
134#[apply(ast)]
135pub enum Ws<'s> {
136 LineComment(&'s str),
137 Space(&'s str),
138 BlockComment(&'s str),
139}
140impl Display for Ws<'_> {
141 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
142 match self {
143 Ws::LineComment(c) => write!(f, "//{c}"),
144 Ws::Space(s) => write!(f, "{s}"),
145 Ws::BlockComment(c) => write!(f, "/*{c}*/"),
146 }
147 }
148}
149impl Ws<'_> {
150 pub fn is_comment(&self) -> bool {
151 !matches!(self, Self::Space(_))
152 }
153
154 pub fn is_line_comment(&self) -> bool {
155 matches!(self, Self::LineComment(_))
156 }
157}
158
159#[apply(ast)]
160#[derive(Default)]
161pub struct Whitespace<'s>(pub Vec<Ws<'s>>);
162
163impl Display for Whitespace<'_> {
164 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
165 self.0.iter().try_for_each(|ws| write!(f, "{ws}"))
166 }
167}
168
169#[apply(ast)]
170pub struct Separated<'s, T> {
171 pub values: Vec<WsWrapped<'s, T>>,
172 pub trailing_comma: bool,
175 pub trailing_ws: Whitespace<'s>,
176}
177
178impl<T: Display> Display for Separated<'_, T> {
179 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
180 let Self {
181 values,
182 trailing_comma,
183 trailing_ws,
184 } = self;
185 let Some((last, rest)) = values.split_last() else {
186 return write!(f, "{trailing_ws}")
187 };
188 rest.iter().try_for_each(|i| write!(f, "{i},"))?;
189 write!(f, "{last}")?;
190 if *trailing_comma {
191 write!(f, ",")?;
192 }
193 write!(f, "{trailing_ws}")
194 }
195}
196
197#[apply(ast)]
198pub struct ExtentionIdent {}
199
200fn file(input: &str) -> IResult<File> {
201 map(
202 tuple((
203 context("attributes", many0(ws_lead(extention_attr))),
204 context("value", ws_lead(value::value)),
205 ws,
206 )),
207 |(extentions, value, trailing_ws)| File {
208 extentions,
209 value,
210 trailing_ws,
211 },
212 )(input)
213}
214
215#[allow(unused)]
216fn dbg_dmp<'a, O>(
217 context: impl Display,
218 mut p: impl Parser<&'a str, O, Error<'a>>,
219) -> impl FnMut(&'a str) -> IResult<O> {
220 move |s: &str| match p.parse(s) {
221 Err(nom::Err::Error(c)) => {
222 println!("{}: Parsing Error: {} at:\n{:?}", context, c, s);
223 Err(nom::Err::Error(c))
224 }
225 Err(e) => {
226 println!("{}: Error({}) at:\n{:?}", context, e, s);
227 Err(e)
228 }
229 a => a,
230 }
231}
232
233fn extention_attr(input: &str) -> IResult<Attribute> {
234 map(
235 tuple((
236 char('#'),
237 ws,
238 char('!'),
239 ws,
240 char('['),
241 ws,
242 tag("enable"),
243 ws,
244 char('('),
245 separated(ident),
246 char(')'),
247 ws,
248 char(']'),
249 )),
250 |(
251 _,
252 after_pound,
253 _,
254 after_exclamation,
255 _,
256 after_bracket,
257 _,
258 after_ident,
259 _,
260 extentions,
261 _,
262 after_paren,
263 _,
264 )| {
265 Attribute {
266 after_pound,
267 after_exclamation,
268 extentions,
269 after_bracket,
270 after_enable: after_ident,
271 after_paren,
272 }
273 },
274 )(input)
275}
276
277fn ident(input: &str) -> IResult<&str> {
278 alt((
279 recognize(pair(
280 tag("r#"),
281 cut(many1(alt((take_while1(is_xid_continue), is_a("+-."))))),
282 )),
283 recognize(pair(
284 alt((tag("_"), take_while_m_n(1, 1, is_xid_start))),
286 take_while(is_xid_continue),
287 )),
288 ))(input)
289}
290
291fn block_comment(input: &str) -> IResult<Ws> {
292 map(
293 delimited(
294 tag("/*"),
295 recognize(many0(alt((
296 is_not("*/"),
297 recognize(block_comment),
298 take_until1("*/"),
299 )))),
300 tag("*/"),
301 ),
302 Ws::BlockComment,
303 )(input)
304}
305
306fn ws(input: &str) -> IResult<Whitespace> {
307 map(
308 many0(alt((
309 map(is_a("\n\t\r "), Ws::Space),
310 map(
311 preceded(tag("//"), recognize(pair(is_not("\n"), line_ending))),
312 Ws::LineComment,
313 ),
314 block_comment,
315 ))),
316 Whitespace,
317 )(input)
318}
319
320fn separated<'a, T, P: Parser<&'a str, T, Error<'a>>>(
321 p: P,
322) -> impl FnMut(&'a str) -> IResult<Separated<T>> {
323 context(
324 "separated",
325 map(
326 tuple((
327 separated_list0(char(','), ws_wrapped(p)),
328 opt(char(',')),
329 ws,
330 )),
331 |(values, trailing_comma, trailing_ws)| Separated {
332 values,
333 trailing_comma: trailing_comma.is_some(),
334 trailing_ws,
335 },
336 ),
337 )
338}
339
340struct FormatOption<'a, T>(&'a Option<T>);
341impl<T: Display> Display for FormatOption<'_, T> {
342 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
343 match self.0 {
344 Some(t) => write!(f, "{t}"),
345 None => Ok(()),
346 }
347 }
348}
349
350#[cfg(test)]
351mod test {
352 use include_dir::include_dir;
353
354 use super::*;
355
356 const INPUTS: include_dir::Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/input");
357
358 #[test]
359 fn whitespace() {
360 assert_eq!(
361 block_comment("/* a */ ").finish().unwrap(),
362 (" ", Ws::BlockComment(" a ".into()))
363 );
364 assert_eq!(
365 block_comment("/* a /* inner */ */ ").finish().unwrap(),
366 (" ", Ws::BlockComment(" a /* inner */ ".into()))
367 );
368 assert_eq!(
369 ws("/* *//* a /* inner */ */").finish().unwrap(),
370 (
371 "",
372 Whitespace(vec![
373 Ws::BlockComment(" ".into()),
374 Ws::BlockComment(" a /* inner */ ".into())
375 ])
376 )
377 );
378 }
379
380 #[test]
381 fn ensure_valid_ron() {
382 for file in INPUTS.entries() {
383 let _: ron::Value = ron::de::from_bytes(file.as_file().unwrap().contents()).unwrap();
384 }
385 }
386
387 #[test]
388 fn parse() {
389 for file in INPUTS.entries() {
390 let contents = file.as_file().unwrap().contents_utf8().unwrap();
391 let ron: File = contents.try_into().map_err(|e| eprintln!("{e}")).unwrap();
392
393 assert_eq!(ron.to_string(), contents);
394
395 insta::with_settings!({snapshot_path => "../tests/snapshots"},{
396 insta::assert_ron_snapshot!(file.path().display().to_string(), ron);
397 })
398 }
399 }
400}