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