use winnow::combinator::cut_err; use winnow::combinator::delimited; use winnow::combinator::opt; use winnow::combinator::separated1; use winnow::trace::trace; use crate::parser::trivia::ws_comment_newline; use crate::parser::value::value; use crate::{Array, Item, RawString, Value}; use crate::parser::prelude::*; // ;; Array // array = array-open array-values array-close pub(crate) fn array<'i>(check: RecursionCheck) -> impl Parser, Array, ContextError> { trace("array", move |input: &mut Input<'i>| { delimited( ARRAY_OPEN, cut_err(array_values(check)), cut_err(ARRAY_CLOSE) .context(StrContext::Label("array")) .context(StrContext::Expected(StrContextValue::CharLiteral(']'))), ) .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','; // note: this rule is modified // array-values = [ ( array-value array-sep array-values ) / // array-value / ws-comment-newline ] pub(crate) fn array_values<'i>( check: RecursionCheck, ) -> impl Parser, Array, ContextError> { move |input: &mut Input<'i>| { let check = check.recursing(input)?; ( opt( (separated1(array_value(check), ARRAY_SEP), opt(ARRAY_SEP)).map( |(v, trailing): (Vec, Option)| { ( Array::with_vec(v.into_iter().map(Item::Value).collect()), trailing.is_some(), ) }, ), ), ws_comment_newline.span(), ) .try_map::<_, _, std::str::Utf8Error>(|(array, trailing)| { let (mut array, comma) = array.unwrap_or_default(); array.set_trailing_comma(comma); array.set_trailing(RawString::with_span(trailing)); Ok(array) }) .parse_next(input) } } pub(crate) fn array_value<'i>( check: RecursionCheck, ) -> impl Parser, Value, ContextError> { move |input: &mut Input<'i>| { ( ws_comment_newline.span(), value(check), ws_comment_newline.span(), ) .map(|(ws1, v, ws2)| v.decorated(RawString::with_span(ws1), RawString::with_span(ws2))) .parse_next(input) } } #[cfg(test)] 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(Default::default()).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(Default::default()).parse(new_input(input)); if let Ok(parsed) = &mut parsed { parsed.despan(input); } assert!(parsed.is_err()); } } }