diff --git a/crates/hcl-edit/src/parser/expr.rs b/crates/hcl-edit/src/parser/expr.rs index 7d086e59..f825d768 100644 --- a/crates/hcl-edit/src/parser/expr.rs +++ b/crates/hcl-edit/src/parser/expr.rs @@ -26,11 +26,32 @@ use winnow::combinator::{ }; use winnow::token::{any, none_of, one_of, take}; +fn ws_or_sp<'i, 's>( + state: &'s RefCell, +) -> impl Parser, (), ContextError> + 's { + move |input: &mut Input<'i>| { + if state.borrow().newlines_allowed() { + ws.parse_next(input) + } else { + sp.parse_next(input) + } + } +} + pub(super) fn expr(input: &mut Input) -> PResult { - let state = RefCell::new(ExprParseState::default()); + parse_expr(RefCell::default(), input) +} + +fn expr_with_state<'i, 's>( + state: &'s RefCell, +) -> impl Parser, Expression, ContextError> + 's { + move |input: &mut Input<'i>| parse_expr(state.clone(), input) +} + +#[inline] +fn parse_expr(state: RefCell, input: &mut Input) -> PResult { expr_inner(&state).parse_next(input)?; - let expr = state.into_inner().into_expr(); - Ok(expr) + Ok(state.into_inner().into_expr()) } fn expr_inner<'i, 's>( @@ -45,7 +66,7 @@ fn expr_inner<'i, 's>( // Parse the next whitespace sequence and only add it as decor suffix to the expression if // we actually encounter a traversal, conditional or binary operation. We'll rewind the // parser if none of these follow. - let suffix = sp.span().parse_next(input)?; + let suffix = ws_or_sp(state).span().parse_next(input)?; // Peek the next two bytes to identify the following operation, if any. match peek(take::<_, _, ContextError>(2usize)).parse_next(input) { @@ -182,7 +203,7 @@ fn traversal<'i, 's>( move |input: &mut Input<'i>| { repeat( 1.., - prefix_decorated(sp, traversal_operator.map(Decorated::new)), + prefix_decorated(ws_or_sp(state), traversal_operator.map(Decorated::new)), ) .map(|operators| state.borrow_mut().on_traversal(operators)) .parse_next(input) @@ -249,7 +270,7 @@ fn binary_op<'i, 's>( move |input: &mut Input<'i>| { ( spanned(binary_operator.map(Spanned::new)), - prefix_decorated(sp, expr), + prefix_decorated(ws_or_sp(state), expr_with_state(state)), ) .map(|(operator, rhs_expr)| state.borrow_mut().on_binary_op(operator, rhs_expr)) .parse_next(input) @@ -285,8 +306,14 @@ fn conditional<'i, 's>( ) -> impl Parser, (), ContextError> + 's { move |input: &mut Input<'i>| { ( - preceded(b'?', decorated(sp, expr, sp)), - preceded(cut_char(':'), prefix_decorated(sp, expr)), + preceded( + b'?', + decorated(ws_or_sp(state), expr_with_state(state), ws_or_sp(state)), + ), + preceded( + cut_char(':'), + prefix_decorated(ws_or_sp(state), expr_with_state(state)), + ), ) .map(|(true_expr, false_expr)| state.borrow_mut().on_conditional(true_expr, false_expr)) .parse_next(input) @@ -555,9 +582,10 @@ fn parenthesis<'i, 's>( state: &'s RefCell, ) -> impl Parser, (), ContextError> + 's { move |input: &mut Input<'i>| { + state.borrow_mut().allow_newlines(true); delimited( cut_char('('), - decorated(ws, expr, ws) + decorated(ws, expr_with_state(state), ws) .map(|expr| state.borrow_mut().on_expr_term(Parenthesis::new(expr))), cut_char(')'), ) diff --git a/crates/hcl-edit/src/parser/state.rs b/crates/hcl-edit/src/parser/state.rs index 7742d884..728b2d47 100644 --- a/crates/hcl-edit/src/parser/state.rs +++ b/crates/hcl-edit/src/parser/state.rs @@ -66,6 +66,7 @@ pub(super) struct ExprParseState { unary: Option>, current: Option, ws: Option>, + allow_newlines: bool, } impl ExprParseState { @@ -143,7 +144,26 @@ impl ExprParseState { self.current = Some(expr); } + pub(super) fn allow_newlines(&mut self, allow: bool) { + self.allow_newlines = allow; + } + + pub(super) fn newlines_allowed(&self) -> bool { + self.allow_newlines + } + pub(super) fn into_expr(self) -> Expression { self.current.unwrap() } } + +impl Clone for ExprParseState { + fn clone(&self) -> Self { + ExprParseState { + unary: None, + current: None, + ws: None, + allow_newlines: self.allow_newlines, + } + } +} diff --git a/crates/hcl-edit/tests/regressions.rs b/crates/hcl-edit/tests/regressions.rs index 400aa31b..4ff53420 100644 --- a/crates/hcl-edit/tests/regressions.rs +++ b/crates/hcl-edit/tests/regressions.rs @@ -99,3 +99,69 @@ fn issue_294() { ); } } + +// https://github.com/martinohmann/hcl-rs/issues/319 +#[test] +fn issue_319() { + macro_rules! assert_ok { + ($input:expr) => { + assert!($input.parse::().is_ok()); + }; + } + + macro_rules! assert_err { + ($input:expr) => { + assert!($input.parse::().is_err()); + }; + } + + // single line expressions with parenthesis + assert_ok! {r#" + foo = (true ? "bar" : "baz") + "#}; + assert_ok! {r#" + foo = (1 > 2) + "#}; + assert_ok! {r#" + foo = (var.foo[2]) + "#}; + + // multiline expressions with parenthesis + assert_ok! {r#" + foo = (true ? + "bar" : + "baz" + ) + "#}; + assert_ok! {r#" + foo = ( + 1 + > + 2 + ) + "#}; + assert_ok! {r#" + foo = ( + var + .foo + [2] + ) + "#}; + + // invalid multiline expressions without parenthesis + assert_err! {r#" + foo = true ? + "bar" : + "baz" + "#}; + assert_err! {r#" + foo = 1 + > + 2 + "#}; + assert_err! {r#" + foo = var + .foo + [2] + "#}; +}