-
Notifications
You must be signed in to change notification settings - Fork 4
✨ feat(lang): add do...end block syntax for while, foreach, and match #1045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bd8dd80
2a054e3
169ce94
68bc447
64884a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -376,10 +376,19 @@ impl Formatter { | |
| self.append_space(); | ||
| } | ||
|
|
||
| // Check for 'do' keyword or colon | ||
| let do_index = Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::Do)); | ||
| let colon_index = Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::Colon)); | ||
| let uses_do_syntax = do_index.is_some(); | ||
|
|
||
| // If there's no colon, split before the right parenthesis | ||
| let expr_index = self.calculate_split_position(node, 0); | ||
| // If there's no colon or do, split before the right parenthesis | ||
| let expr_index = if let Some(index) = do_index.or(colon_index) { | ||
| index | ||
| } else { | ||
| Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::RParen)) | ||
| .map(|index| index + 1) | ||
| .unwrap_or(0) | ||
| }; | ||
|
|
||
| node.children.iter().take(expr_index).for_each(|child| { | ||
| self.format_node( | ||
|
|
@@ -394,11 +403,17 @@ impl Formatter { | |
|
|
||
| let mut expr_nodes = node.children.iter().skip(expr_index).peekable(); | ||
|
|
||
| // Format colon if it exists | ||
| if colon_index.is_some() | ||
| && let Some(colon_node) = expr_nodes.next() | ||
| // Format colon or do keyword if it exists | ||
| if (colon_index.is_some() || do_index.is_some()) | ||
| && let Some(separator_node) = expr_nodes.next() | ||
| { | ||
| self.format_colon_with_spacing(colon_node, &mut expr_nodes, block_indent_level + 1); | ||
| if uses_do_syntax { | ||
| // Format 'do' keyword with standardized spacing | ||
| self.format_do_with_spacing(separator_node, &mut expr_nodes); | ||
| } else { | ||
| // Format colon with spacing | ||
| self.format_colon_with_spacing(separator_node, &mut expr_nodes, block_indent_level + 1); | ||
| } | ||
| } | ||
|
|
||
| let block_indent_level = if is_prev_pipe { | ||
|
|
@@ -803,17 +818,28 @@ impl Formatter { | |
|
|
||
| let indent_adjustment = self.calculate_indent_adjustment(); | ||
|
|
||
| // Find the colon position | ||
| let colon_pos = Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::Colon)).unwrap_or(0); | ||
| // Check for 'do' keyword or colon | ||
| let do_pos = Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::Do)); | ||
| let colon_pos = Self::find_token_position(node, |kind| matches!(kind, mq_lang::TokenKind::Colon)); | ||
| let uses_do_syntax = do_pos.is_some(); | ||
| let separator_pos = do_pos.or(colon_pos).unwrap_or(0); | ||
|
|
||
| // Format arguments (lparen, value, rparen) | ||
| for child in node.children.iter().take(colon_pos) { | ||
| for child in node.children.iter().take(separator_pos) { | ||
| self.format_node(mq_lang::Shared::clone(child), 0); | ||
| } | ||
|
|
||
| // Format colon | ||
| if let Some(colon) = node.children.get(colon_pos) { | ||
| self.format_node(mq_lang::Shared::clone(colon), 0); | ||
| // Format colon or do keyword | ||
| let mut remaining_children = node.children.iter().skip(separator_pos).peekable(); | ||
|
|
||
| if let Some(separator) = remaining_children.next() { | ||
| if uses_do_syntax { | ||
| // Format 'do' keyword with standardized spacing | ||
| self.format_do_with_spacing(separator, &mut remaining_children); | ||
| } else { | ||
| // Format colon | ||
| self.format_node(mq_lang::Shared::clone(separator), 0); | ||
| } | ||
| } | ||
|
|
||
| // Calculate indent level for match arms (similar to format_if) | ||
|
|
@@ -827,7 +853,7 @@ impl Formatter { | |
| let end_indent_level = if is_prev_pipe { indent_level + 1 } else { indent_level } + indent_adjustment; | ||
|
|
||
| // Format match arms and end | ||
| let remaining_children: Vec<_> = node.children.iter().skip(colon_pos + 1).collect(); | ||
| let remaining_children: Vec<_> = remaining_children.collect(); | ||
|
||
|
|
||
| // Check if this is a multiline match (first match arm has new line) | ||
| let is_multiline = remaining_children | ||
|
|
@@ -1345,6 +1371,31 @@ impl Formatter { | |
| } | ||
| } | ||
|
|
||
| /// Formats a 'do' keyword with standardized spacing. | ||
| /// Adds space before the 'do' keyword, formats it, and adds space after | ||
| /// if the next node doesn't have a newline and is not a MatchArm. | ||
| fn format_do_with_spacing<'a, I>( | ||
| &mut self, | ||
| do_node: &mq_lang::Shared<mq_lang::CstNode>, | ||
| remaining: &mut std::iter::Peekable<I>, | ||
| ) where | ||
| I: Iterator<Item = &'a mq_lang::Shared<mq_lang::CstNode>>, | ||
| { | ||
| // Add space before 'do' keyword | ||
| self.append_space(); | ||
|
|
||
| // Format the 'do' keyword | ||
| self.format_node(mq_lang::Shared::clone(do_node), 0); | ||
|
|
||
| // Add space after 'do' if next node doesn't have newline and is not a MatchArm | ||
| if let Some(next) = remaining.peek() | ||
| && !next.has_new_line() | ||
| && !matches!(next.kind, mq_lang::CstNodeKind::MatchArm) | ||
| { | ||
| self.append_space(); | ||
| } | ||
| } | ||
|
|
||
| /// Escapes control characters in a string, preserving existing valid escape sequences | ||
| fn escape_string(s: &str) -> String { | ||
| let mut result = String::with_capacity(s.len() * 2); | ||
|
|
@@ -2292,6 +2343,115 @@ end"#, | |
| else do | ||
| test2 | ||
| end | ||
| "# | ||
| )] | ||
| #[case::while_do_end_oneline("while(x > 0) do x - 1 end", "while (x > 0) do x - 1 end")] | ||
| #[case::while_do_end_multiline( | ||
| r#"while(x > 0) do | ||
| let x = x - 1 | x | ||
| end"#, | ||
| r#"while (x > 0) do | ||
| let x = x - 1 | x | ||
| end | ||
| "# | ||
| )] | ||
| #[case::while_do_end_with_break( | ||
| r#"while(x < 10) do | ||
| let x = x + 1 | ||
| | if(x == 3): | ||
| break | ||
| else: | ||
| x | ||
| end"#, | ||
| r#"while (x < 10) do | ||
| let x = x + 1 | ||
| | if (x == 3): | ||
| break | ||
| else: | ||
| x | ||
| end | ||
| "# | ||
| )] | ||
| #[case::foreach_do_end_oneline( | ||
| "foreach(item, arr) do process(item) end", | ||
| "foreach (item, arr) do process(item) end" | ||
| )] | ||
| #[case::foreach_do_end_multiline( | ||
| r#"foreach(x, array(1, 2, 3)) do | ||
| add(x, 1) | ||
| end"#, | ||
| r#"foreach (x, array(1, 2, 3)) do | ||
| add(x, 1) | ||
| end | ||
| "# | ||
| )] | ||
| #[case::foreach_do_end_with_continue( | ||
| r#"foreach(x, array(1, 2, 3, 4, 5)) do | ||
| if(x == 3): | ||
| continue | ||
| else: | ||
| x + 10 | ||
| end"#, | ||
| r#"foreach (x, array(1, 2, 3, 4, 5)) do | ||
| if (x == 3): | ||
| continue | ||
| else: | ||
| x + 10 | ||
| end | ||
| "# | ||
| )] | ||
| #[case::foreach_do_end_nested( | ||
| r#"foreach(row, arr) do | ||
| foreach(x, row) do | ||
| x * 2 | ||
| end | ||
| end"#, | ||
| r#"foreach (row, arr) do | ||
| foreach (x, row) do | ||
| x * 2 | ||
| end | ||
| end | ||
| "# | ||
| )] | ||
| #[case::match_do_end_oneline( | ||
| "match(x) do | 1: \"one\" | 2: \"two\" | _: \"other\" end", | ||
| "match (x) do | 1: \"one\" | 2: \"two\" | _: \"other\" end" | ||
| )] | ||
| #[case::match_do_end_multiline( | ||
| r#"match(2) do | ||
| | 1: "one" | ||
| | 2: "two" | ||
| | _: "other" | ||
| end"#, | ||
| r#"match (2) do | ||
| | 1: "one" | ||
| | 2: "two" | ||
| | _: "other" | ||
| end | ||
| "# | ||
| )] | ||
| #[case::match_do_end_type_pattern( | ||
| r#"match(array(1, 2, 3)) do | ||
| | :array: "is_array" | ||
| | :number: "is_number" | ||
| | _: "other" | ||
| end"#, | ||
| r#"match (array(1, 2, 3)) do | ||
| | :array: "is_array" | ||
| | :number: "is_number" | ||
| | _: "other" | ||
| end | ||
| "# | ||
| )] | ||
| #[case::match_do_end_with_guard( | ||
| r#"match(x) do | ||
| | n if(n > 0): "positive" | ||
| | _: "non-positive" | ||
| end"#, | ||
| r#"match (x) do | ||
| | n if (n > 0): "positive" | ||
| | _: "non-positive" | ||
| end | ||
| "# | ||
| )] | ||
| fn test_format(#[case] code: &str, #[case] expected: &str) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The calculation of
expr_indexhas changed significantly and may not match the originalcalculate_split_positionlogic. The removed code calledself.calculate_split_position(node, 0)which likely had specific logic for determining split positions. The new implementation directly finds the RParen position, which could produce different behavior. Consider verifying that this simplified logic handles all edge cases thatcalculate_split_positionwas designed for.