toml_edit/parser/
array.rs

1use winnow::combinator::cut_err;
2use winnow::combinator::delimited;
3use winnow::combinator::opt;
4use winnow::combinator::separated1;
5use winnow::trace::trace;
6
7use crate::parser::trivia::ws_comment_newline;
8use crate::parser::value::value;
9use crate::{Array, Item, RawString, Value};
10
11use crate::parser::prelude::*;
12
13// ;; Array
14
15// array = array-open array-values array-close
16pub(crate) fn array<'i>(check: RecursionCheck) -> impl Parser<Input<'i>, Array, ContextError> {
17    trace("array", move |input: &mut Input<'i>| {
18        delimited(
19            ARRAY_OPEN,
20            cut_err(array_values(check)),
21            cut_err(ARRAY_CLOSE)
22                .context(StrContext::Label("array"))
23                .context(StrContext::Expected(StrContextValue::CharLiteral(']'))),
24        )
25        .parse_next(input)
26    })
27}
28
29// note: we're omitting ws and newlines here, because
30// they should be part of the formatted values
31// array-open  = %x5B ws-newline  ; [
32pub(crate) const ARRAY_OPEN: u8 = b'[';
33// array-close = ws-newline %x5D  ; ]
34const ARRAY_CLOSE: u8 = b']';
35// array-sep = ws %x2C ws  ; , Comma
36const ARRAY_SEP: u8 = b',';
37
38// note: this rule is modified
39// array-values = [ ( array-value array-sep array-values ) /
40//                  array-value / ws-comment-newline ]
41pub(crate) fn array_values<'i>(
42    check: RecursionCheck,
43) -> impl Parser<Input<'i>, Array, ContextError> {
44    move |input: &mut Input<'i>| {
45        let check = check.recursing(input)?;
46        (
47            opt(
48                (separated1(array_value(check), ARRAY_SEP), opt(ARRAY_SEP)).map(
49                    |(v, trailing): (Vec<Value>, Option<u8>)| {
50                        (
51                            Array::with_vec(v.into_iter().map(Item::Value).collect()),
52                            trailing.is_some(),
53                        )
54                    },
55                ),
56            ),
57            ws_comment_newline.span(),
58        )
59            .try_map::<_, _, std::str::Utf8Error>(|(array, trailing)| {
60                let (mut array, comma) = array.unwrap_or_default();
61                array.set_trailing_comma(comma);
62                array.set_trailing(RawString::with_span(trailing));
63                Ok(array)
64            })
65            .parse_next(input)
66    }
67}
68
69pub(crate) fn array_value<'i>(
70    check: RecursionCheck,
71) -> impl Parser<Input<'i>, Value, ContextError> {
72    move |input: &mut Input<'i>| {
73        (
74            ws_comment_newline.span(),
75            value(check),
76            ws_comment_newline.span(),
77        )
78            .map(|(ws1, v, ws2)| v.decorated(RawString::with_span(ws1), RawString::with_span(ws2)))
79            .parse_next(input)
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86
87    #[test]
88    fn arrays() {
89        let inputs = [
90            r#"[]"#,
91            r#"[   ]"#,
92            r#"[
93  1, 2, 3
94]"#,
95            r#"[
96  1,
97  2, # this is ok
98]"#,
99            r#"[# comment
100# comment2
101
102
103   ]"#,
104            r#"[# comment
105# comment2
106      1
107
108#sd
109,
110# comment3
111
112   ]"#,
113            r#"[1]"#,
114            r#"[1,]"#,
115            r#"[ "all", 'strings', """are the same""", '''type''']"#,
116            r#"[ 100, -2,]"#,
117            r#"[1, 2, 3]"#,
118            r#"[1.1, 2.1, 3.1]"#,
119            r#"["a", "b", "c"]"#,
120            r#"[ [ 1, 2 ], [3, 4, 5] ]"#,
121            r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#,
122            r#"[ { x = 1, a = "2" }, {a = "a",b = "b",     c =    "c"} ]"#,
123        ];
124        for input in inputs {
125            dbg!(input);
126            let mut parsed = array(Default::default()).parse(new_input(input));
127            if let Ok(parsed) = &mut parsed {
128                parsed.despan(input);
129            }
130            assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned()));
131        }
132    }
133
134    #[test]
135    fn invalid_arrays() {
136        let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#];
137        for input in invalid_inputs {
138            dbg!(input);
139            let mut parsed = array(Default::default()).parse(new_input(input));
140            if let Ok(parsed) = &mut parsed {
141                parsed.despan(input);
142            }
143            assert!(parsed.is_err());
144        }
145    }
146}