diff --git a/docs/language/control-flow.md b/docs/language/control-flow.md index 282a59ba..ad0bc40f 100644 --- a/docs/language/control-flow.md +++ b/docs/language/control-flow.md @@ -65,3 +65,37 @@ elif condition { ``` ![](../assets/ifelif.png){width="200"} + +## Ternary Expressions + +Embed conditional logic directly inside expressions. + +### Syntax + +```goboscript +if () else +``` + +### Examples + +```goboscript +say if (score > 100) "winner" else "loser"; +``` + +Ternaries can be nested — an `else` branch can itself be a ternary: + +```goboscript +say if (condition_A) "A_true" + else if (condition_B) "B_true" + else "false"; +``` + +When used as a condition, a ternary must be wrapped in parentheses: + +```goboscript +say if (if (condition_A) true else false) "yes" else "no"; +``` + +### Compilation + +Ternaries are desugared at compile time into `if`/`else` branches. Each branch receives a copy of the surrounding statement with the ternary substituted for the appropriate value. diff --git a/src/ast/expr.rs b/src/ast/expr.rs index b9d778c0..e0cc6f31 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -64,6 +64,11 @@ pub enum Expr { property: SmolStr, span: Span, }, + Ternary { + condition: Box, + tvalue: Box, + fvalue: Box, + }, } impl Expr { @@ -79,6 +84,7 @@ impl Expr { Self::BinOp { span, .. } => span.clone(), Self::StructLiteral { span, .. } => span.clone(), Self::Property { span, .. } => span.clone(), + Self::Ternary { condition, .. } => condition.span(), } } } diff --git a/src/codegen/sb3.rs b/src/codegen/sb3.rs index 63a6a8df..96cb5d41 100644 --- a/src/codegen/sb3.rs +++ b/src/codegen/sb3.rs @@ -1276,6 +1276,7 @@ where T: Write + Seek property, span, } => self.property(s, d, this_id, parent_id, object, property, span), + Expr::Ternary { .. } => unreachable!(), } } } diff --git a/src/frontend/build.rs b/src/frontend/build.rs index 6927dea7..db34b33f 100644 --- a/src/frontend/build.rs +++ b/src/frontend/build.rs @@ -135,7 +135,8 @@ pub fn build_impl( } { let mut fs = fs.borrow_mut(); - visitor::pass0::visit_project( + visitor::ternary::visit_project(&mut project); + visitor::pass0::visit_project( &mut *fs, &input, &mut project, diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index 94d2454e..27b18ea2 100644 --- a/src/parser/grammar.lalrpop +++ b/src/parser/grammar.lalrpop @@ -37,7 +37,7 @@ Declr: () = { SET_ROTATION_STYLE_ALL_AROUND ";" => { sprite.rotation_style = RotationStyle::AllAround; }, - SET_ROTATION_STYLE_DO_NOT_ROTATE ";" => { + SET_ROTATION_STYLE_DO_NOT_ROTATE ";" => { sprite.rotation_style = RotationStyle::DoNotRotate; }, PROC > => { @@ -373,117 +373,128 @@ Kwarg: (Option<(SmolStr, Span)>, Expr) = { } } +// Top-level expression: ternary lives here so that IF is in FIRST(Expr), +// allowing nested ternaries like `if (if (c) t else f) v else w` without parens. Expr: Expr = { + IF "(" ")" ELSE => { + Expr::Ternary { + condition: Box::new(condition), + tvalue: Box::new(tvalue), + fvalue: Box::new(fvalue), + } + }, + BinExpr, +} + +// Precedence-climbing grammar for binary/unary operators, no ternary. +BinExpr: Expr = { #[precedence(level="1")] Term, StructLiteral, #[precedence(level="2")] #[assoc(side="right")] - "-" => UnOp::Minus .to_expr(l..r, e), - NOT => UnOp::Not .to_expr(l..r, e), - LENGTH => UnOp::Length .to_expr(l..r, e), - ROUND => UnOp::Round .to_expr(l..r, e), - ABS => UnOp::Abs .to_expr(l..r, e), - FLOOR => UnOp::Floor .to_expr(l..r, e), - CEIL => UnOp::Ceil .to_expr(l..r, e), - SQRT => UnOp::Sqrt .to_expr(l..r, e), - SIN => UnOp::Sin .to_expr(l..r, e), - COS => UnOp::Cos .to_expr(l..r, e), - TAN => UnOp::Tan .to_expr(l..r, e), - ASIN => UnOp::Asin .to_expr(l..r, e), - ACOS => UnOp::Acos .to_expr(l..r, e), - ATAN => UnOp::Atan .to_expr(l..r, e), - LN => UnOp::Ln .to_expr(l..r, e), - LOG => UnOp::Log .to_expr(l..r, e), - ANTILN => UnOp::AntiLn .to_expr(l..r, e), - ANTILOG => UnOp::AntiLog.to_expr(l..r, e), + "-" => UnOp::Minus .to_expr(l..r, e), + NOT => UnOp::Not .to_expr(l..r, e), + LENGTH => UnOp::Length .to_expr(l..r, e), + ROUND => UnOp::Round .to_expr(l..r, e), + ABS => UnOp::Abs .to_expr(l..r, e), + FLOOR => UnOp::Floor .to_expr(l..r, e), + CEIL => UnOp::Ceil .to_expr(l..r, e), + SQRT => UnOp::Sqrt .to_expr(l..r, e), + SIN => UnOp::Sin .to_expr(l..r, e), + COS => UnOp::Cos .to_expr(l..r, e), + TAN => UnOp::Tan .to_expr(l..r, e), + ASIN => UnOp::Asin .to_expr(l..r, e), + ACOS => UnOp::Acos .to_expr(l..r, e), + ATAN => UnOp::Atan .to_expr(l..r, e), + LN => UnOp::Ln .to_expr(l..r, e), + LOG => UnOp::Log .to_expr(l..r, e), + ANTILN => UnOp::AntiLn .to_expr(l..r, e), + ANTILOG => UnOp::AntiLog.to_expr(l..r, e), #[precedence(level="3")] #[assoc(side="left")] - "*" => BinOp::Mul .to_expr(l..r, lhs, rhs), - "/" => BinOp::Div .to_expr(l..r, lhs, rhs), - "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs), - "%" => BinOp::Mod .to_expr(l..r, lhs, rhs), + "*" => BinOp::Mul .to_expr(l..r, lhs, rhs), + "/" => BinOp::Div .to_expr(l..r, lhs, rhs), + "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs), + "%" => BinOp::Mod .to_expr(l..r, lhs, rhs), #[precedence(level="4")] #[assoc(side="left")] - "+" => BinOp::Add .to_expr(l..r, lhs, rhs), - "-" => BinOp::Sub .to_expr(l..r, lhs, rhs), + "+" => BinOp::Add .to_expr(l..r, lhs, rhs), + "-" => BinOp::Sub .to_expr(l..r, lhs, rhs), #[precedence(level="5")] #[assoc(side="left")] - "<" => BinOp::Lt .to_expr(l..r, lhs, rhs), - "<=" => BinOp::Le .to_expr(l..r, lhs, rhs), - ">" => BinOp::Gt .to_expr(l..r, lhs, rhs), - ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs), + "<" => BinOp::Lt .to_expr(l..r, lhs, rhs), + "<=" => BinOp::Le .to_expr(l..r, lhs, rhs), + ">" => BinOp::Gt .to_expr(l..r, lhs, rhs), + ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs), #[precedence(level="6")] #[assoc(side="right")] - "&" => BinOp::Join .to_expr(l..r, lhs, rhs), - // "|>" "(" ")" => { - // args.insert(0, lhs); - // if let Some(repr) = Repr::from_shape(&name, args.len()) { - // Expr::Repr { repr, span: l..r, args } - // } else { - // Expr::FuncCall { name: name, span: l..r, args: args } - // } - // }, + "&" => BinOp::Join .to_expr(l..r, lhs, rhs), #[precedence(level="7")] #[assoc(side="left")] - IN => BinOp::In .to_expr(l..r, lhs, rhs), - NOT IN => UnOp::Not.to_expr(l..r, BinOp::In.to_expr(l..r, lhs, rhs)), - "==" => BinOp::Eq .to_expr(l..r, lhs, rhs), - "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs), + IN => BinOp::In.to_expr(l..r, lhs, rhs), + NOT IN => UnOp::Not.to_expr(l..r, BinOp::In.to_expr(l..r, lhs, rhs)), + "==" => BinOp::Eq .to_expr(l..r, lhs, rhs), + "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs), #[precedence(level="8")] #[assoc(side="left")] - AND => BinOp::And .to_expr(l..r, lhs, rhs), + AND => BinOp::And .to_expr(l..r, lhs, rhs), #[precedence(level="9")] #[assoc(side="left")] - OR => BinOp::Or .to_expr(l..r, lhs, rhs), + OR => BinOp::Or .to_expr(l..r, lhs, rhs), } +// Same split for IfExpr (used in statement conditions, where `>` can't be a +// comparison operator since it is consumed by ONLOUDNESS/ONTIMER productions). IfExpr: Expr = { + IF "(" ")" ELSE => { + Expr::Ternary { + condition: Box::new(condition), + tvalue: Box::new(tvalue), + fvalue: Box::new(fvalue), + } + }, + IfBinExpr, +} + +IfBinExpr: Expr = { #[precedence(level="1")] Term, #[precedence(level="2")] #[assoc(side="right")] - "-" => UnOp::Minus .to_expr(l..r, e), - NOT => UnOp::Not .to_expr(l..r, e), - LENGTH => UnOp::Length .to_expr(l..r, e), - ROUND => UnOp::Round .to_expr(l..r, e), - ABS => UnOp::Abs .to_expr(l..r, e), - FLOOR => UnOp::Floor .to_expr(l..r, e), - CEIL => UnOp::Ceil .to_expr(l..r, e), - SQRT => UnOp::Sqrt .to_expr(l..r, e), - SIN => UnOp::Sin .to_expr(l..r, e), - COS => UnOp::Cos .to_expr(l..r, e), - TAN => UnOp::Tan .to_expr(l..r, e), - ASIN => UnOp::Asin .to_expr(l..r, e), - ACOS => UnOp::Acos .to_expr(l..r, e), - ATAN => UnOp::Atan .to_expr(l..r, e), - LN => UnOp::Ln .to_expr(l..r, e), - LOG => UnOp::Log .to_expr(l..r, e), - ANTILN => UnOp::AntiLn .to_expr(l..r, e), - ANTILOG => UnOp::AntiLog.to_expr(l..r, e), + "-" => UnOp::Minus .to_expr(l..r, e), + NOT => UnOp::Not .to_expr(l..r, e), + LENGTH => UnOp::Length .to_expr(l..r, e), + ROUND => UnOp::Round .to_expr(l..r, e), + ABS => UnOp::Abs .to_expr(l..r, e), + FLOOR => UnOp::Floor .to_expr(l..r, e), + CEIL => UnOp::Ceil .to_expr(l..r, e), + SQRT => UnOp::Sqrt .to_expr(l..r, e), + SIN => UnOp::Sin .to_expr(l..r, e), + COS => UnOp::Cos .to_expr(l..r, e), + TAN => UnOp::Tan .to_expr(l..r, e), + ASIN => UnOp::Asin .to_expr(l..r, e), + ACOS => UnOp::Acos .to_expr(l..r, e), + ATAN => UnOp::Atan .to_expr(l..r, e), + LN => UnOp::Ln .to_expr(l..r, e), + LOG => UnOp::Log .to_expr(l..r, e), + ANTILN => UnOp::AntiLn .to_expr(l..r, e), + ANTILOG => UnOp::AntiLog.to_expr(l..r, e), #[precedence(level="3")] #[assoc(side="left")] - "*" => BinOp::Mul .to_expr(l..r, lhs, rhs), - "/" => BinOp::Div .to_expr(l..r, lhs, rhs), - "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs), - "%" => BinOp::Mod .to_expr(l..r, lhs, rhs), + "*" => BinOp::Mul .to_expr(l..r, lhs, rhs), + "/" => BinOp::Div .to_expr(l..r, lhs, rhs), + "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs), + "%" => BinOp::Mod .to_expr(l..r, lhs, rhs), #[precedence(level="4")] #[assoc(side="left")] - "+" => BinOp::Add .to_expr(l..r, lhs, rhs), - "-" => BinOp::Sub .to_expr(l..r, lhs, rhs), + "+" => BinOp::Add .to_expr(l..r, lhs, rhs), + "-" => BinOp::Sub .to_expr(l..r, lhs, rhs), #[precedence(level="5")] #[assoc(side="left")] - "<" => BinOp::Lt .to_expr(l..r, lhs, rhs), - "<=" => BinOp::Le .to_expr(l..r, lhs, rhs), - ">" => BinOp::Gt .to_expr(l..r, lhs, rhs), - ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs), + "<" => BinOp::Lt .to_expr(l..r, lhs, rhs), + "<=" => BinOp::Le .to_expr(l..r, lhs, rhs), + ">" => BinOp::Gt .to_expr(l..r, lhs, rhs), + ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs), #[precedence(level="6")] #[assoc(side="right")] - "&" => BinOp::Join .to_expr(l..r, lhs, rhs), - // "|>" "(" ")" => { - // args.insert(0, lhs); - // if let Some(repr) = Repr::from_shape(&name, args.len()) { - // Expr::Repr { repr, span: l..r, args } - // } else { - // Expr::FuncCall { name: name, span: l..r, args: args } - // } - // }, + "&" => BinOp::Join .to_expr(l..r, lhs, rhs), #[precedence(level="7")] #[assoc(side="left")] - IN => BinOp::In .to_expr(l..r, lhs, rhs), - NOT IN => UnOp::Not.to_expr(l..r, BinOp::In.to_expr(l..r, lhs, rhs)), - "==" => BinOp::Eq .to_expr(l..r, lhs, rhs), - "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs), + IN => BinOp::In.to_expr(l..r, lhs, rhs), + NOT IN => UnOp::Not.to_expr(l..r, BinOp::In.to_expr(l..r, lhs, rhs)), + "==" => BinOp::Eq .to_expr(l..r, lhs, rhs), + "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs), #[precedence(level="8")] #[assoc(side="left")] - AND => BinOp::And .to_expr(l..r, lhs, rhs), + AND => BinOp::And .to_expr(l..r, lhs, rhs), #[precedence(level="9")] #[assoc(side="left")] - OR => BinOp::Or .to_expr(l..r, lhs, rhs), + OR => BinOp::Or .to_expr(l..r, lhs, rhs), } Term: Expr = { @@ -496,8 +507,8 @@ Term: Expr = { => Value::from(v).to_expr(l..r), => Value::from(v).to_expr(l..r), => Value::from(v).to_expr(l..r), - => Expr::Name(Name::Name { name: n, span: l..r }), - => Expr::Arg(Name::Name { name: n, span: l..r }), + => Expr::Name(Name::Name { name: n, span: l..r }), + => Expr::Arg(Name::Name { name: n, span: l..r }), "(" ")" => { let (args, kwargs) = split_args(args); if let Some(repr) = Repr::from_shape(&name, args.len()) { @@ -527,7 +538,7 @@ StructLiteralField: StructLiteralField = { } }; -Value: (Value, Span) = { +Value: (Value, Span) = { TRUE => (Value::from(1.0), l..r), FALSE => (Value::from(0.0), l..r), => (Value::from(v), l..r), @@ -539,7 +550,7 @@ Value: (Value, Span) = { "-" => (Value::un_op(UnOp::Minus, &v.0), v.1), } -ConstExpr: ConstExpr = { +ConstExpr: ConstExpr = { => ConstExpr::Value { value: v.0, span: v.1 }, "." => ConstExpr::EnumVariant { enum_name, variant_name, enum_name_span: el..er, variant_name_span: vl..vr, @@ -707,8 +718,8 @@ extern { SET_SIZE => Token::SetSize, POINT_IN_DIRECTION => Token::PointInDirection, SET_VOLUME => Token::SetVolume, - SET_ROTATION_STYLE_LEFT_RIGHT => Token::SetRotationStyleLeftRight, - SET_ROTATION_STYLE_ALL_AROUND => Token::SetRotationStyleAllAround, + SET_ROTATION_STYLE_LEFT_RIGHT => Token::SetRotationStyleLeftRight, + SET_ROTATION_STYLE_ALL_AROUND => Token::SetRotationStyleAllAround, SET_ROTATION_STYLE_DO_NOT_ROTATE => Token::SetRotationStyleDoNotRotate, VAR => Token::Var, } diff --git a/src/visitor.rs b/src/visitor.rs index 9d827e7c..6583ca98 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -1,6 +1,7 @@ -pub mod pass0; -pub mod pass1; -pub mod pass2; -pub mod pass3; -pub mod pass4; -mod transformations; +pub mod pass0; +pub mod pass1; +pub mod pass2; +pub mod pass3; +pub mod pass4; +pub mod ternary; +mod transformations; diff --git a/src/visitor/pass1.rs b/src/visitor/pass1.rs index 42cc9983..33e459c9 100644 --- a/src/visitor/pass1.rs +++ b/src/visitor/pass1.rs @@ -310,6 +310,7 @@ fn visit_expr(expr: &mut Expr, before: &mut Vec, s: &mut S) { visit_expr(object, before, s); None } + Expr::Ternary { .. } => unreachable!(), }; if let Some(replace) = replace { *expr = replace; diff --git a/src/visitor/pass2.rs b/src/visitor/pass2.rs index 22b990ca..ffe245cd 100644 --- a/src/visitor/pass2.rs +++ b/src/visitor/pass2.rs @@ -407,6 +407,7 @@ fn visit_expr(expr: &mut Expr, s: S, d: D) { Expr::Property { object, .. } => { visit_expr(object, s, d); } + Expr::Ternary { .. } => unreachable!(), } transformations::apply(expr, |expr| transformations::enum_field_access(expr, s, d)); transformations::apply(expr, transformations::minus); diff --git a/src/visitor/pass3.rs b/src/visitor/pass3.rs index a436fc15..d2383cad 100644 --- a/src/visitor/pass3.rs +++ b/src/visitor/pass3.rs @@ -238,5 +238,6 @@ fn visit_expr(expr: &Expr, s: &mut S) { } => { visit_expr(object, s); } + Expr::Ternary { .. } => unreachable!(), } } diff --git a/src/visitor/ternary.rs b/src/visitor/ternary.rs new file mode 100644 index 00000000..5611be40 --- /dev/null +++ b/src/visitor/ternary.rs @@ -0,0 +1,284 @@ +use crate::ast::{ + Expr, + Project, + Stmt, +}; + +pub fn visit_project(project: &mut Project) { + visit_sprite(&mut project.stage); + for sprite in project.sprites.values_mut() { + visit_sprite(sprite); + } +} + +fn visit_sprite(sprite: &mut crate::ast::Sprite) { + for body in sprite.proc_definitions.values_mut() { + extract_ternary_from_stmts(body); + } + for body in sprite.func_definitions.values_mut() { + extract_ternary_from_stmts(body); + } + for event in &mut sprite.events { + extract_ternary_from_stmts(&mut event.body); + } +} + +pub fn extract_ternary_from_stmts(stmts: &mut [Stmt]) { + for stmt in stmts { + extract_ternary_from_stmt(stmt); + } +} + +fn extract_ternary_from_stmt(stmt: &mut Stmt) { + while let Some(condition) = stmt_find_closest_ternary(stmt) { + let condition = condition.clone(); + let mut tbranch = stmt.clone(); + let mut fbranch = stmt.clone(); + assert!(stmt_split_closest_ternary(&mut tbranch, true)); + assert!(stmt_split_closest_ternary(&mut fbranch, false)); + *stmt = Stmt::Branch { + cond: Box::new(condition), + if_body: vec![tbranch], + else_body: vec![fbranch], + } + } + match stmt { + Stmt::Repeat { body, .. } => extract_ternary_from_stmts(body), + Stmt::Forever { body, .. } => extract_ternary_from_stmts(body), + Stmt::Branch { + if_body, else_body, .. + } => { + extract_ternary_from_stmts(if_body); + extract_ternary_from_stmts(else_body); + } + Stmt::Until { body, .. } => extract_ternary_from_stmts(body), + _ => {} + } +} + +fn stmt_find_closest_ternary(stmt: &Stmt) -> Option<&Expr> { + match stmt { + Stmt::Forever { .. } => None, + Stmt::Show(..) => None, + Stmt::Hide(..) => None, + Stmt::DeleteList(..) => None, + Stmt::Repeat { times, .. } => expr_find_closest_ternary(times), + Stmt::Branch { cond, .. } => expr_find_closest_ternary(cond), + Stmt::Until { cond, .. } => expr_find_closest_ternary(cond), + Stmt::SetVar { value, .. } => expr_find_closest_ternary(value), + Stmt::ChangeVar { value, .. } => expr_find_closest_ternary(value), + Stmt::AddToList { value, .. } => expr_find_closest_ternary(value), + Stmt::DeleteListIndex { index, .. } => expr_find_closest_ternary(index), + Stmt::InsertAtList { index, value, .. } => { + expr_find_closest_ternary(index).or_else(|| expr_find_closest_ternary(value)) + } + Stmt::SetListIndex { index, value, .. } => { + expr_find_closest_ternary(index).or_else(|| expr_find_closest_ternary(value)) + } + Stmt::Block { args, kwargs, .. } => { + for arg in args { + if let Some(found) = expr_find_closest_ternary(arg) { + return Some(found); + } + } + for (_, kwarg) in kwargs.values() { + if let Some(found) = expr_find_closest_ternary(kwarg) { + return Some(found); + } + } + return None; + } + Stmt::ProcCall { args, kwargs, .. } => { + for arg in args { + if let Some(found) = expr_find_closest_ternary(arg) { + return Some(found); + } + } + for (_, kwarg) in kwargs.values() { + if let Some(found) = expr_find_closest_ternary(kwarg) { + return Some(found); + } + } + return None; + } + Stmt::FuncCall { args, kwargs, .. } => { + for arg in args { + if let Some(found) = expr_find_closest_ternary(arg) { + return Some(found); + } + } + for (_, kwarg) in kwargs.values() { + if let Some(found) = expr_find_closest_ternary(kwarg) { + return Some(found); + } + } + return None; + } + Stmt::Return { value, .. } => expr_find_closest_ternary(value), + } +} + +fn expr_find_closest_ternary(expr: &Expr) -> Option<&Expr> { + match expr { + Expr::Value { .. } => None, + Expr::Name(..) => None, + Expr::Arg(..) => None, + Expr::Dot { lhs, .. } => expr_find_closest_ternary(lhs), + Expr::Repr { args, .. } => { + for arg in args { + if let Some(found) = expr_find_closest_ternary(arg) { + return Some(found); + } + } + return None; + } + Expr::FuncCall { args, kwargs, .. } => { + for arg in args { + if let Some(found) = expr_find_closest_ternary(arg) { + return Some(found); + } + } + for (_, kwarg) in kwargs.values() { + if let Some(found) = expr_find_closest_ternary(kwarg) { + return Some(found); + } + } + return None; + } + Expr::UnOp { opr, .. } => expr_find_closest_ternary(opr), + Expr::BinOp { lhs, rhs, .. } => { + expr_find_closest_ternary(lhs).or_else(|| expr_find_closest_ternary(rhs)) + } + Expr::StructLiteral { fields, .. } => { + for field in fields { + if let Some(found) = expr_find_closest_ternary(&field.value) { + return Some(found); + } + } + return None; + } + Expr::Property { object, .. } => expr_find_closest_ternary(object), + Expr::Ternary { condition, .. } => Some(condition), + } +} + +fn stmt_split_closest_ternary(stmt: &mut Stmt, condition: bool) -> bool { + match stmt { + Stmt::Forever { .. } => false, + Stmt::Show(..) => false, + Stmt::Hide(..) => false, + Stmt::DeleteList(..) => false, + Stmt::Repeat { times, .. } => expr_split_closest_ternary(times, condition), + Stmt::Branch { cond, .. } => expr_split_closest_ternary(cond, condition), + Stmt::Until { cond, .. } => expr_split_closest_ternary(cond, condition), + Stmt::SetVar { value, .. } => expr_split_closest_ternary(value, condition), + Stmt::ChangeVar { value, .. } => expr_split_closest_ternary(value, condition), + Stmt::AddToList { value, .. } => expr_split_closest_ternary(value, condition), + Stmt::DeleteListIndex { index, .. } => expr_split_closest_ternary(index, condition), + Stmt::InsertAtList { index, value, .. } => { + expr_split_closest_ternary(index, condition) + || expr_split_closest_ternary(value, condition) + } + Stmt::SetListIndex { index, value, .. } => { + expr_split_closest_ternary(index, condition) + || expr_split_closest_ternary(value, condition) + } + Stmt::Block { args, kwargs, .. } => { + for arg in args { + if expr_split_closest_ternary(arg, condition) { + return true; + } + } + for (_, kwarg) in kwargs.values_mut() { + if expr_split_closest_ternary(kwarg, condition) { + return true; + } + } + false + } + Stmt::ProcCall { args, kwargs, .. } => { + for arg in args { + if expr_split_closest_ternary(arg, condition) { + return true; + } + } + for (_, kwarg) in kwargs.values_mut() { + if expr_split_closest_ternary(kwarg, condition) { + return true; + } + } + false + } + Stmt::FuncCall { args, kwargs, .. } => { + for arg in args { + if expr_split_closest_ternary(arg, condition) { + return true; + } + } + for (_, kwarg) in kwargs.values_mut() { + if expr_split_closest_ternary(kwarg, condition) { + return true; + } + } + false + } + Stmt::Return { value, .. } => expr_split_closest_ternary(value, condition), + } +} + +fn expr_split_closest_ternary(expr: &mut Expr, condition: bool) -> bool { + let mut replace: Option = None; + match expr { + Expr::Value { .. } => {} + Expr::Name(..) => {} + Expr::Arg(..) => {} + Expr::Dot { lhs, .. } => return expr_split_closest_ternary(lhs, condition), + Expr::Repr { args, .. } => { + for arg in args { + if expr_split_closest_ternary(arg, condition) { + return true; + } + } + return false; + } + Expr::FuncCall { args, kwargs, .. } => { + for arg in args { + if expr_split_closest_ternary(arg, condition) { + return true; + } + } + for (_, kwarg) in kwargs.values_mut() { + if expr_split_closest_ternary(kwarg, condition) { + return true; + } + } + return false; + } + Expr::UnOp { opr, .. } => return expr_split_closest_ternary(opr, condition), + Expr::BinOp { lhs, rhs, .. } => { + return expr_split_closest_ternary(lhs, condition) + || expr_split_closest_ternary(rhs, condition) + } + Expr::StructLiteral { fields, .. } => { + for field in fields { + if expr_split_closest_ternary(&mut field.value, condition) { + return true; + } + } + return false; + } + Expr::Property { object, .. } => return expr_split_closest_ternary(object, condition), + Expr::Ternary { tvalue, fvalue, .. } => { + if condition { + replace = Some(*tvalue.clone()); + } else { + replace = Some(*fvalue.clone()); + } + } + }; + let result = replace.is_some(); + if let Some(replace) = replace { + *expr = replace; + } + result +}