diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b9b2f00..9561bca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +- Added support for `if else` ternary expressions + ## [0.7.0] - 2025-11-11 ### Added diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 417eabaa..abf24816 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for `if else` ternary expressions + ## 0.7.0 - 2025-11-11 ### Added diff --git a/core/datatests/generators/logical_line_parser.rs b/core/datatests/generators/logical_line_parser.rs index cf2d6584..8bff8dc6 100644 --- a/core/datatests/generators/logical_line_parser.rs +++ b/core/datatests/generators/logical_line_parser.rs @@ -31,6 +31,7 @@ pub fn generate_test_files(root_dir: &Path) { routine_implementations::generate(root_dir); control_flows::generate(root_dir); statements::generate(root_dir); + ternary::generate(root_dir); attributes::generate(root_dir); semicolons::generate(root_dir); regression::generate(root_dir); @@ -3258,6 +3259,25 @@ mod statements { } } +mod ternary { + use super::*; + + pub fn generate(root_dir: &Path) { + generate_test_cases!( + root_dir, + assignment = " + _|A := if A then B else C; + ", + nested = " + _|A := if if A then B else C then + if A then B else C + else + if A then B else C; + ", + ); + } +} + mod attributes { use super::*; diff --git a/core/datatests/generators/optimising_line_formatter.rs b/core/datatests/generators/optimising_line_formatter.rs index d7d0f00d..a2cf1053 100644 --- a/core/datatests/generators/optimising_line_formatter.rs +++ b/core/datatests/generators/optimising_line_formatter.rs @@ -432,6 +432,31 @@ mod comments { and FFFFFFFFFFFF then ; ", + ternary = " + A := + if A then + B + else // + if C then D else E; + A := + if A then + B + else // + if CCCCC then + D + else + E; + A := + if A then + B + else // + if C then + DDDDD // + else if a then + E + else + d; + ", ); } } @@ -3013,16 +3038,16 @@ mod control_flows { inline = " if A then else if AAAAAA and BBBBBB then - Bar; + Bar else if AAAAAAAAA and BBBBBBBBB then - Bar; + Bar else if AAAAAAAAAA and BBBBBBBBBB and CCCCCCCCCC then - Bar; + Bar else if Foo(AAA, BB, CCC) then - Bar; + Bar else if Foo( AAAA, BBBBBB, @@ -3052,7 +3077,7 @@ mod control_flows { end else if AA(A, B, C) then begin Bar; - end; + end else if AAA(AAA, BB, CCC) then begin Bar; @@ -4238,6 +4263,7 @@ mod expressions { unary::generate(root_dir); set::generate(root_dir); strings::generate(root_dir); + ternary::generate(root_dir); } mod boolean { @@ -4859,6 +4885,114 @@ mod expressions { ); } } + + mod ternary { + use super::*; + + pub fn generate(root_dir: &Path) { + generate_test_cases!( + root_dir, + basic_assignment = " + A := if B then C else D; + AAAAAAAA := + if B then C else D; + AAAAAAAA := + if BBBBBBB then + CC + else + DD; + ", + nested_must_wrap = " + // wrap_column=41 | + // A := if B then if C then D else E else F; + A := + if B then + if C then D else E + else + F; + ", + nested = " + A := + if B then + if CCCCC then D else E + else + F; + A := + if B then + if CCCCCC then + D + else + E + else + F; + A := + if BBBBBBB + BBBBBBBB then + if CCCCCC + CCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + EEEEEE + else + FFFFFFFF + FFFFFFFFFF; + A := + if BBBBBBBB + + BBBBBBBB then + if CCCCC + CCCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + EEEEEE + else + FFFFFFFF + FFFFFFFFFF; + A := + if BBBBBBB + BBBBBBBB then + if CCCCCCC + + CCCCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + EEEEEE + else + FFFFFFFF + FFFFFFFFFF; + A := + if BBBBBBB + BBBBBBBB then + if CCCCC + CCCCCC then + DDDDDDDDDD + + DDDDDD + else + EEEEEEEEE + EEEEEE + else + FFFFFFFF + FFFFFFFFFF; + A := + if BBBBBBB + BBBBBBBB then + if CCCCC + CCCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + + EEEEEEE + else + FFFFFFFF + FFFFFFFFFF; + A := + if BBBBBBB + BBBBBBBB then + if CCCCCC + CCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + EEEEEE + else + FFFFFFFFF + + FFFFFFFFFF; + A := + if BBBBBBBB + + BBBBBBBB then + if CCCCCC + + CCCCCC then + DDDDDDDDDD + DDDDD + else + EEEEEEEEE + + EEEEEEE + else + FFFFFFFFF + FFFFFFFFF; + ", + ); + } + } } mod attributes { diff --git a/core/src/rules/optimising_line_formatter/contexts.rs b/core/src/rules/optimising_line_formatter/contexts.rs index 8f09bcad..f8661678 100644 --- a/core/src/rules/optimising_line_formatter/contexts.rs +++ b/core/src/rules/optimising_line_formatter/contexts.rs @@ -97,6 +97,7 @@ pub(super) enum ContextType { // Nested Type, Precedence(u8), + IfElse, AssignLHS, AssignRHS, ControlFlowBegin, @@ -105,6 +106,7 @@ pub(super) enum ContextType { MemberAccess, Subject, GuardClause, + ExpressionGuardClause, AnonHeader, } use ContextType as CT; @@ -519,6 +521,15 @@ impl<'a> SpecificContextStack<'a> { (Some(TT::Op(OK::Colon)), _) => { self.update_last_matching_context(node, context_matches!(_), apply_pivotal_break); } + (Some(TT::Keyword(KK::Then)), _) => { + // if-else expression `then` + self.update_last_matching_context(node, CT::IfElse, |_, data| { + data.is_broken |= is_break; + data.can_break &= is_break; + data.one_element_per_line.get_or_insert(is_break); + }); + self.update_last_matching_context(node, CT::Subject, apply_pivotal_break); + } (Some(TT::Keyword(KK::If | KK::While | KK::Until | KK::On | KK::Case)), _) => { self.update_last_matching_context(node, CT::ControlFlowBegin, |_, data| { data.is_broken |= is_break; @@ -540,9 +551,13 @@ impl<'a> SpecificContextStack<'a> { }); } (Some(TT::Keyword(KK::Else)), _) => { - self.update_last_matching_context(node, CT::ControlFlowBegin, |_, data| { - data.is_broken |= is_break; - }); + self.update_last_matching_context( + node, + context_matches!(CT::ControlFlowBegin | CT::Subject), + |_, data| { + data.is_broken |= is_break; + }, + ); } (_, Some(TT::Keyword(KK::Begin | KK::End))) => { self.update_last_matching_context(node, CT::ControlFlowBegin, |_, data| { @@ -648,7 +663,7 @@ impl<'a> SpecificContextStack<'a> { } } (prev, Some(op @ (TT::Op(_) | TT::Keyword(_)))) - if super::get_operator_precedence(op).is_some() && is_binary(op, prev) => + if op.get_operator_precedence().is_some() && is_binary(op, prev.as_ref()) => { self.update_operator_precedences(node, is_break); } @@ -690,6 +705,7 @@ impl<'a> SpecificContextStack<'a> { CT::TypedAssignment | CT::ForLoop | CT::RaiseAt => { data.is_broken |= data.is_child_broken } + CT::ExpressionGuardClause => data.is_broken |= data.is_child_broken, CT::AssignLHS if self.formatting_contexts.line.get_line_type() == LLT::Assignment => { @@ -775,12 +791,6 @@ impl<'a> LineFormattingContexts<'a> { token_types: &'a [TokenType], context_tree: &'a ParentPointerTree, ) -> Self { - let get_token_type_from_line_index = |line_index| { - token_types - .get(*line.get_tokens().get(line_index as usize)?) - .cloned() - }; - let builder_context_tree = Self::new_tree(); let mut contexts = LineFormattingContextsBuilder::new(&builder_context_tree); @@ -822,18 +832,35 @@ impl<'a> LineFormattingContexts<'a> { } } - let mut prev_prev_token_type = None; - let mut prev_token_type = None; - let mut prev_semantic_token_type = None; - let mut current = get_token_type_from_line_index(0); - let mut next_token_type = get_token_type_from_line_index(1); + let mut prev_token_types: Vec = Vec::with_capacity(line.get_tokens().len()); + macro_rules! last_semantic_token_type { + () => { + last_semantic_token_type!(0) + }; + ($i: expr) => { + prev_token_types + .iter() + .rev() + .filter(|tt| !tt.is_comment_or_directive()) + .nth($i) + }; + } + let mut next_token_types = line + .get_tokens() + .iter() + .rev() + .map(|id| token_types[*id]) + .collect::>(); + let mut current = next_token_types.pop(); while let Some(current_token_type) = current { if !current_token_type.is_comment_or_compiler_directive() { let last_context_type = contexts.current_context.get().context_type; // New contexts relating to the previous token are pushed here // to avoid including any leading comments - if let (Some(prev_token_type), Some(prev_directive_token_type)) = - (prev_token_type, prev_semantic_token_type) + if let Some(prev_token_type) = prev_token_types + .iter() + .rev() + .find(|tt| !tt.is_comment_or_compiler_directive()) { match (prev_token_type, last_context_type) { (TT::Op(OK::LParen | OK::LBrack | OK::LessThan(ChK::Generic)), _) @@ -858,7 +885,7 @@ impl<'a> LineFormattingContexts<'a> { } _ => {} } - match prev_directive_token_type { + match prev_token_type { TT::Keyword(KK::Of) => { contexts.push(CT::Subject); contexts.push_expression(); @@ -884,7 +911,26 @@ impl<'a> LineFormattingContexts<'a> { contexts.push_expression(); } TT::Keyword(KK::If | KK::While | KK::On | KK::Until | KK::Case) => { - contexts.push(CT::GuardClause); + contexts.push( + if prev_token_types + .iter() + .filter(|tt| !tt.is_comment_or_compiler_directive()) + .count() + == 1 + { + CT::GuardClause + } else { + CT::ExpressionGuardClause + }, + ); + contexts.push_expression(); + } + TT::Keyword(KK::Then) => { + // if-else expressions + contexts.push_expression(); + } + TT::Keyword(KK::Else) => { + // if-else expressions contexts.push_expression(); } TT::Keyword(KK::With) => { @@ -908,7 +954,10 @@ impl<'a> LineFormattingContexts<'a> { contexts.push_expression(); } TT::Keyword(KK::Abstract) - if matches!(prev_prev_token_type, Some(TT::Keyword(KK::Class))) => {} + if matches!( + last_semantic_token_type!(1), + Some(TT::Keyword(KK::Class)) + ) => {} TT::Keyword(kk) if kk.is_directive() => { contexts.push_expression(); } @@ -933,8 +982,8 @@ impl<'a> LineFormattingContexts<'a> { TT::ConditionalDirective(kind) if kind.is_else() => { contexts.push_operators(); } - op if super::get_operator_precedence(op).is_some() - && is_binary(op, prev_prev_token_type) => + op if op.get_operator_precedence().is_some() + && is_binary(*op, last_semantic_token_type!(1)) => { contexts.push_operators(); } @@ -952,7 +1001,7 @@ impl<'a> LineFormattingContexts<'a> { TT::Op(OK::LessThan(ChevronKind::Generic)) => BracketKind::Angle, _ => BracketKind::Round, }; - let (typ, cont_delta) = match prev_token_type { + let (typ, cont_delta) = match last_semantic_token_type!() { // routine invocations Some(TT::Identifier | TT::Op(OK::GreaterThan(ChevronKind::Generic))) => { (BracketStyle::BreakClose, 1) @@ -1063,12 +1112,48 @@ impl<'a> LineFormattingContexts<'a> { _ => {} } } + TT::Keyword(KK::If) if contexts.line_index != 0 => { + // if-else expression + if let Some(tt) = prev_token_types.last() + && tt != &TT::Keyword(KK::Else) + { + // Don't push another context in the `else if` case + contexts.push((CT::IfElse, 0)); + } + } TT::Keyword(KK::If | KK::While | KK::With | KK::On) => { contexts.push_utility((CT::ControlFlowBegin, 0)); contexts.push(CT::ControlFlow); } TT::Keyword(KK::Else) => { - contexts.pop_until(CT::ControlFlowBegin); + if let Some(CT::IfElse) = + contexts.pop_until(context_matches!(CT::ControlFlowBegin | CT::IfElse)) + { + // This is to ensure nested if-else expressions are popped of the stack. + // They will already have their `ending_token` set. + while contexts.last_context_mut().ending_token.is_some() { + contexts.pop(); + contexts.pop_until(CT::IfElse); + } + if contexts.last_context_mut().context_type == CT::IfElse + && next_token_types + .iter() + .rev() + .find(|tt| !tt.is_comment_or_compiler_directive()) + .is_some_and(|tt| tt != &TT::Keyword(KK::If)) + { + // Only set the ending on bare `else`, i.e., not `else if` + contexts.last_context_mut().ending_token = Some(contexts.line_index) + } + } + if next_token_types + .iter() + .next_back() + .is_some_and(|tt| tt != &TT::Keyword(KK::If)) + { + // `Subject` shouldn't be pushed if it is an `else if` + contexts.push(CT::Subject); + } } TT::Keyword(KK::Case | KK::Until) => { contexts.push(CT::ControlFlow); @@ -1094,7 +1179,10 @@ impl<'a> LineFormattingContexts<'a> { } TT::Op(OK::Dot) if CT::Precedence(0) == last_context_type => { contexts.retain_current(); - if matches!(prev_token_type, Some(TT::Op(OK::RParen | OK::RBrack))) { + if matches!( + last_semantic_token_type!(), + Some(TT::Op(OK::RParen | OK::RBrack)) + ) { /* Fluency is considered after () and [] because they allow for arbitrary computation which will @@ -1108,16 +1196,25 @@ impl<'a> LineFormattingContexts<'a> { contexts.fluent(contexts.current_context.clone()); } } - op if super::get_operator_precedence(op).is_some() - && is_binary(op, prev_token_type) => + + op if op.get_operator_precedence().is_some() + && is_binary(op, last_semantic_token_type!()) => { - let op_prec = super::get_operator_precedence(op).unwrap(); + let op_prec = op.get_operator_precedence().unwrap(); contexts.pop_until_and_retain(CT::Precedence(op_prec)); } - TT::Keyword(KK::Of) if matches!(next_token_type, Some(TT::Keyword(KK::Object))) => { + TT::Keyword(KK::Of) + if matches!(next_token_types.last(), Some(TT::Keyword(KK::Object))) => + { contexts.pop_until_after(CT::AnonHeader); } - TT::Keyword(KK::Then | KK::Do | KK::Of) => { + TT::Keyword(KK::Then) => { + contexts.pop_until_and_retain(context_matches!(CT::GuardClause)); + contexts.pop_until(context_matches!(CT::IfElse | CT::ControlFlow)); + contexts.push(CT::Subject); + } + TT::Keyword(KK::Do | KK::Of) => { + contexts.pop_until_and_retain(context_matches!(CT::GuardClause)); contexts.pop_until(context_matches!(CT::ControlFlow | CT::ForLoop)); } TT::Keyword(KK::Function | KK::Procedure) @@ -1135,7 +1232,7 @@ impl<'a> LineFormattingContexts<'a> { contexts.pop_until_after(CT::AnonHeader); } TT::Keyword(KK::Abstract) - if matches!(prev_token_type, Some(TT::Keyword(KK::Class))) => {} + if matches!(last_semantic_token_type!(), Some(TT::Keyword(KK::Class))) => {} TT::Keyword(kk) if kk.is_directive() => { if contexts.pop_until(CT::DirectiveList) != Some(CT::DirectiveList) { if contexts @@ -1180,15 +1277,8 @@ impl<'a> LineFormattingContexts<'a> { _ => {} } - if !current_token_type.is_comment_or_directive() { - prev_prev_token_type = prev_token_type; - prev_token_type = current; - } - if !current_token_type.is_comment_or_compiler_directive() { - prev_semantic_token_type = current; - } - current = next_token_type; - next_token_type = get_token_type_from_line_index(contexts.line_index + 1); + prev_token_types.extend(current); + current = next_token_types.pop(); } contexts.finalise(); @@ -1480,9 +1570,12 @@ impl<'builder> LineFormattingContextsBuilder<'builder> { self.contexts_to_remove .extend(self.contexts.into_iter().filter(|node_ref| { let ctx = node_ref.get(); - ctx.ending_token == Some(ctx.starting_token) - || ctx.ending_token.is_none() - && matches!(ctx.context_type, CT::CommaList | CT::Assignment) + let is_single_token = ctx.ending_token == Some(ctx.starting_token) + // `self.line_index` is the the index after the last token + || ctx.starting_token + 1 == self.line_index; + let is_commalist_or_assignment = ctx.ending_token.is_none() + && matches!(ctx.context_type, CT::CommaList | CT::Assignment); + is_single_token || is_commalist_or_assignment })); // If an `Brackets` context is in an expression or guard clause, then @@ -1779,9 +1872,11 @@ mod tests { "TypedAssignment" => Ok(CT::TypedAssignment), "AssignLHS" => Ok(CT::AssignLHS), "AssignRHS" => Ok(CT::AssignRHS), + "IfElse" => Ok(CT::IfElse), "ControlFlow" => Ok(CT::ControlFlow), "GuardClause" => Ok(CT::GuardClause), + "EGuardClause" => Ok(CT::ExpressionGuardClause), "ControlFlowBegin" => Ok(CT::ControlFlowBegin), "MemberAccess" => Ok(CT::MemberAccess), "ForLoop" => Ok(CT::ForLoop), @@ -2087,7 +2182,6 @@ mod tests { 1 Assignment ^----$-------- 1 AssignLHS ^----$ 1 BGenerics ^--$ - 1 AssignRHS ^---- "}, commas = {" type AA = class end; @@ -2096,7 +2190,6 @@ mod tests { 1 AssignLHS ^------------$ 1 BGenerics ^----------$ 0 CommaList ^--------$ - 1 AssignRHS ^---- "}, semicolons = {" type AA = class end; @@ -2105,7 +2198,6 @@ mod tests { 1 AssignLHS ^------------$ 1 BGenerics ^----------$ 0 SemicolonList ^--------$ - 1 AssignRHS ^---- "}, constraints = {" type AA = class end; @@ -2117,7 +2209,6 @@ mod tests { 1 SemicolonElem ^--------------------$ 0 CommaList ^----------------$ 1 SemicolonElem ^--------$ - 1 AssignRHS ^---- "}, )] fn generics(input: &str) -> Result<(), DslParseError> { @@ -2213,6 +2304,51 @@ mod tests { assert_contexts(input) } + #[yare::parameterized( + assignment = {" + A := if AA = BB then CC + DD else EE - FF; + 1 Base ^----------------------------------------- + 1 AssignRHS ^----------------------------------$ + 0 IfElse ^--------------------------$-------- + 1 EGuardClause ^-----$ + 1 Precedence(4) ^-----$ + 1 Subject ^----------$ + 1 Precedence(3) ^-----$ + 1 Subject ^----------$ + 1 Precedence(3) ^-----$ + "}, + nested = {" + A := if AA = BB then if CC = DD then EE + FF else GG - HH else HH * II; + 1 Base ^---------------------------------------------------------------------- + 1 AssignRHS ^---------------------------------------------------------------$ + 0 IfElse ^-------------------------------------------------------$-------- + 1 EGuardClause ^-----$ + 1 Precedence(4) ^-----$ + 1 Subject ^---------------------------------------$ + 0 IfElse ^--------------------------$-------- + 1 EGuardClause ^-----$ + 1 Precedence(4) ^-----$ + 1 Subject ^----------$ + 1 Precedence(3) ^-----$ + 1 Subject ^----------$ + 1 Precedence(3) ^-----$ + 1 Subject ^----------$ + 1 Precedence(2) ^-----$ + "}, + else_if = {" + A := if AA then BB else if CC then DD else EE; + 1 Base ^--------------------------------------------- + 1 AssignRHS ^--------------------------------------$ + 0 IfElse ^-----------------------------------$--- + 1 Subject ^-----$ + 1 Subject ^-----$ + 1 Subject ^-----$ + "}, + )] + fn ternary(input: &str) -> Result<(), DslParseError> { + assert_contexts(input) + } + #[yare::parameterized( inline = {" with AA do; diff --git a/core/src/rules/optimising_line_formatter/mod.rs b/core/src/rules/optimising_line_formatter/mod.rs index 6bd926e8..469751ae 100644 --- a/core/src/rules/optimising_line_formatter/mod.rs +++ b/core/src/rules/optimising_line_formatter/mod.rs @@ -1258,41 +1258,47 @@ impl<'this> InternalOptimisingLineFormatter<'this, '_> { const HIGHEST_PRECEDENCE: u8 = 0; const LOWEST_PRECEDENCE: u8 = 5; -fn get_operator_precedence(token_type: TokenType) -> Option { - match token_type { - TT::Op(OK::Dot) => Some(0), +trait OperatorPrecedence { + fn get_operator_precedence(self) -> Option; +} - TT::Op(OK::AddressOf) | TT::Keyword(KK::Not) => Some(1), +impl OperatorPrecedence for TokenType { + fn get_operator_precedence(self) -> Option { + match self { + TT::Op(OK::Dot) => Some(0), - TT::Op(OK::Star | OK::Slash) - | TT::Keyword(KK::Div | KK::Mod | KK::And | KK::Shl | KK::Shr | KK::As) => Some(2), + TT::Op(OK::AddressOf) | TT::Keyword(KK::Not) => Some(1), - TT::Op(OK::Plus | OK::Minus) | TT::Keyword(KK::Or | KK::Xor) => Some(3), + TT::Op(OK::Star | OK::Slash) + | TT::Keyword(KK::Div | KK::Mod | KK::And | KK::Shl | KK::Shr | KK::As) => Some(2), - TT::Op( - OK::Equal(EqKind::Comp) - | OK::NotEqual - | OK::LessThan(ChK::Comp) - | OK::GreaterThan(ChK::Comp) - | OK::LessEqual - | OK::GreaterEqual, - ) - | TT::Keyword(KK::In(InKind::Op) | KK::Is) => Some(4), - // The import clause `in`s is most simply represented as a precedence - // relationship - TT::Keyword(KK::In(InKind::Import)) => Some(4), - TT::Op(OK::DotDot) => Some(5), - - TT::Op(_) - | TT::Identifier - | TT::Keyword(_) - | TT::TextLiteral(_) - | TT::NumberLiteral(_) - | TT::ConditionalDirective(_) - | TT::CompilerDirective - | TT::Comment(_) - | TT::Eof - | TT::Unknown => None, + TT::Op(OK::Plus | OK::Minus) | TT::Keyword(KK::Or | KK::Xor) => Some(3), + + TT::Op( + OK::Equal(EqKind::Comp) + | OK::NotEqual + | OK::LessThan(ChK::Comp) + | OK::GreaterThan(ChK::Comp) + | OK::LessEqual + | OK::GreaterEqual, + ) + | TT::Keyword(KK::In(InKind::Op) | KK::Is) => Some(4), + // The import clause `in`s is most simply represented as a precedence + // relationship + TT::Keyword(KK::In(InKind::Import)) => Some(4), + TT::Op(OK::DotDot) => Some(5), + + TT::Op(_) + | TT::Identifier + | TT::Keyword(_) + | TT::TextLiteral(_) + | TT::NumberLiteral(_) + | TT::ConditionalDirective(_) + | TT::CompilerDirective + | TT::Comment(_) + | TT::Eof + | TT::Unknown => None, + } } } @@ -1301,7 +1307,7 @@ fn get_operator_precedence(token_type: TokenType) -> Option { /// /// Returns whether the given [`TokenType`] is a binary operator by looking at /// its previous (real) token type -fn is_binary(token_type: TokenType, prev_token_type: Option) -> bool { +fn is_binary(token_type: TokenType, prev_token_type: Option<&TokenType>) -> bool { match token_type { TT::Op(OK::Plus | OK::Minus | OK::AddressOf) | TT::Keyword(KK::Not) => {} _ => return true, diff --git a/core/src/rules/optimising_line_formatter/requirements.rs b/core/src/rules/optimising_line_formatter/requirements.rs index 8d352021..5de062d1 100644 --- a/core/src/rules/optimising_line_formatter/requirements.rs +++ b/core/src/rules/optimising_line_formatter/requirements.rs @@ -1,10 +1,10 @@ use super::InternalOptimisingLineFormatter; use super::SpecificContextDataStack; use super::contexts::*; -use super::get_operator_precedence; use super::is_binary; use super::types::DecisionRequirement; use crate::lang::*; +use crate::rules::optimising_line_formatter::OperatorPrecedence; use super::contexts::ContextType as CT; use super::types::DecisionRequirement as DR; @@ -214,7 +214,7 @@ impl InternalOptimisingLineFormatter<'_, '_> { .map(|(_, data)| data.is_broken | data.is_child_broken) .if_else_or_default(DR::MustBreak, DR::Indifferent), (prev, Some(op @ (TT::Op(_) | TT::Keyword(_)))) - if get_operator_precedence(op).is_some() && is_binary(op, prev) => + if op.get_operator_precedence().is_some() && is_binary(op, prev.as_ref()) => { contexts_data .iter() @@ -239,7 +239,7 @@ impl InternalOptimisingLineFormatter<'_, '_> { .map(|(_, data)| data.is_broken | data.is_child_broken) .if_else_or_default(DR::MustBreak, DR::Indifferent), (Some(op @ (TT::Op(_) | TT::Keyword(_))), _) - if get_operator_precedence(op).is_some() => + if op.get_operator_precedence().is_some() => { DR::MustNotBreak } @@ -278,7 +278,20 @@ impl InternalOptimisingLineFormatter<'_, '_> { .get_last_context(CT::ControlFlowBegin) .map(|(_, data)| data.is_child_broken) .if_else_or_default(DR::MustBreak, DR::Indifferent), - (_, Some(TT::Keyword(KK::Else))) => DR::MustBreak, + (_, Some(TT::Keyword(KK::Else))) => contexts_data + .get_last_context(context_matches!(CT::IfElse)) + .map(|(_, data)| data.is_broken | data.is_child_broken) + .if_else_or(DR::MustBreak, DR::MustNotBreak, DR::MustBreak), + (Some(TT::Keyword(KK::Then)), Some(TT::Keyword(KK::If))) => DR::MustBreak, + (Some(TT::Keyword(KK::Then)), _) => contexts_data + .get_last_context(context_matches!(CT::IfElse)) + .and_then(|(_, data)| data.one_element_per_line) + .if_else_or_default(DR::MustBreak, DR::MustNotBreak), + (Some(TT::Keyword(KK::Else)), Some(TT::Keyword(KK::If))) => DR::MustNotBreak, + (Some(TT::Keyword(KK::Else)), _) => contexts_data + .get_last_context(context_matches!(CT::IfElse)) + .map(|(_, data)| data.is_broken | data.is_child_broken) + .if_else_or(DR::MustBreak, DR::MustNotBreak, DR::MustBreak), (Some(_), Some(TT::Keyword(KK::Begin))) => contexts_data .get_last_context(context_matches!(CT::CommaElem | CT::AssignRHS)) .and_then(|(_, data)| data.break_anonymous_routine)