| use winnow::combinator::cut_err; |
| use winnow::combinator::delimited; |
| use winnow::combinator::opt; |
| use winnow::combinator::peek; |
| use winnow::combinator::separated; |
| use winnow::combinator::trace; |
| |
| use crate::parser::trivia::ws_comment_newline; |
| use crate::parser::value::value; |
| use crate::{Array, Item, RawString}; |
| |
| use crate::parser::prelude::*; |
| |
| // ;; Array |
| |
| // array = array-open array-values array-close |
| pub(crate) fn array<'i>(input: &mut Input<'i>) -> PResult<Array> { |
| trace("array", move |input: &mut Input<'i>| { |
| delimited( |
| ARRAY_OPEN, |
| cut_err(array_values), |
| cut_err(ARRAY_CLOSE) |
| .context(StrContext::Label("array")) |
| .context(StrContext::Expected(StrContextValue::CharLiteral(']'))), |
| ) |
| .parse_next(input) |
| }) |
| .parse_next(input) |
| } |
| |
| // note: we're omitting ws and newlines here, because |
| // they should be part of the formatted values |
| // array-open = %x5B ws-newline ; [ |
| pub(crate) const ARRAY_OPEN: u8 = b'['; |
| // array-close = ws-newline %x5D ; ] |
| const ARRAY_CLOSE: u8 = b']'; |
| // array-sep = ws %x2C ws ; , Comma |
| const ARRAY_SEP: u8 = b','; |
| |
| // array-values = ws-comment-newline val ws-comment-newline array-sep array-values |
| // array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] |
| pub(crate) fn array_values(input: &mut Input<'_>) -> PResult<Array> { |
| if peek(opt(ARRAY_CLOSE)).parse_next(input)?.is_some() { |
| // Optimize for empty arrays, avoiding `value` from being expected to fail |
| return Ok(Array::new()); |
| } |
| |
| let array = separated(0.., array_value, ARRAY_SEP).parse_next(input)?; |
| let mut array = Array::with_vec(array); |
| if !array.is_empty() { |
| let comma = opt(ARRAY_SEP).parse_next(input)?.is_some(); |
| array.set_trailing_comma(comma); |
| } |
| let trailing = ws_comment_newline.span().parse_next(input)?; |
| array.set_trailing(RawString::with_span(trailing)); |
| |
| Ok(array) |
| } |
| |
| pub(crate) fn array_value(input: &mut Input<'_>) -> PResult<Item> { |
| let prefix = ws_comment_newline.span().parse_next(input)?; |
| let value = value.parse_next(input)?; |
| let suffix = ws_comment_newline.span().parse_next(input)?; |
| let value = value.decorated(RawString::with_span(prefix), RawString::with_span(suffix)); |
| let value = Item::Value(value); |
| Ok(value) |
| } |
| |
| #[cfg(test)] |
| #[cfg(feature = "parse")] |
| #[cfg(feature = "display")] |
| mod test { |
| use super::*; |
| |
| #[test] |
| fn arrays() { |
| let inputs = [ |
| r#"[]"#, |
| r#"[ ]"#, |
| r#"[ |
| 1, 2, 3 |
| ]"#, |
| r#"[ |
| 1, |
| 2, # this is ok |
| ]"#, |
| r#"[# comment |
| # comment2 |
| |
| |
| ]"#, |
| r#"[# comment |
| # comment2 |
| 1 |
| |
| #sd |
| , |
| # comment3 |
| |
| ]"#, |
| r#"[1]"#, |
| r#"[1,]"#, |
| r#"[ "all", 'strings', """are the same""", '''type''']"#, |
| r#"[ 100, -2,]"#, |
| r#"[1, 2, 3]"#, |
| r#"[1.1, 2.1, 3.1]"#, |
| r#"["a", "b", "c"]"#, |
| r#"[ [ 1, 2 ], [3, 4, 5] ]"#, |
| r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#, |
| r#"[ { x = 1, a = "2" }, {a = "a",b = "b", c = "c"} ]"#, |
| ]; |
| for input in inputs { |
| dbg!(input); |
| let mut parsed = array.parse(new_input(input)); |
| if let Ok(parsed) = &mut parsed { |
| parsed.despan(input); |
| } |
| assert_eq!(parsed.map(|a| a.to_string()), Ok(input.to_owned())); |
| } |
| } |
| |
| #[test] |
| fn invalid_arrays() { |
| let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#]; |
| for input in invalid_inputs { |
| dbg!(input); |
| let mut parsed = array.parse(new_input(input)); |
| if let Ok(parsed) = &mut parsed { |
| parsed.despan(input); |
| } |
| assert!(parsed.is_err()); |
| } |
| } |
| } |