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(name:"array", parser:move |input: &mut Input<'i>| {
18 delimitedimpl Parser, …>(
19 ARRAY_OPEN,
20 second:cut_err(array_values(check)),
21 third: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: Range, v: Value, ws2: Range)| v.decorated(prefix:RawString::with_span(ws1), suffix:RawString::with_span(ws2)))
79 .parse_next(input)
80 }
81}
82
83#[cfg(test)]
84#[cfg(feature = "parse")]
85#[cfg(feature = "display")]
86mod test {
87 use super::*;
88
89 #[test]
90 fn arrays() {
91 let inputs = [
92 r#"[]"#,
93 r#"[ ]"#,
94 r#"[
95 1, 2, 3
96]"#,
97 r#"[
98 1,
99 2, # this is ok
100]"#,
101 r#"[# comment
102# comment2
103
104
105 ]"#,
106 r#"[# comment
107# comment2
108 1
109
110#sd
111,
112# comment3
113
114 ]"#,
115 r#"[1]"#,
116 r#"[1,]"#,
117 r#"[ "all", 'strings', """are the same""", '''type''']"#,
118 r#"[ 100, -2,]"#,
119 r#"[1, 2, 3]"#,
120 r#"[1.1, 2.1, 3.1]"#,
121 r#"["a", "b", "c"]"#,
122 r#"[ [ 1, 2 ], [3, 4, 5] ]"#,
123 r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#,
124 r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#,
125 ];
126 for input in inputs {
127 dbg!(input);
128 let mut parsed = array(Default::default()).parse(new_input(input));
129 if let Ok(parsed) = &mut parsed {
130 parsed.despan(input);
131 }
132 assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned()));
133 }
134 }
135
136 #[test]
137 fn invalid_arrays() {
138 let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#];
139 for input in invalid_inputs {
140 dbg!(input);
141 let mut parsed = array(Default::default()).parse(new_input(input));
142 if let Ok(parsed) = &mut parsed {
143 parsed.despan(input);
144 }
145 assert!(parsed.is_err());
146 }
147 }
148}
149