From 965389f79b2cc20722b8befed0ed91c4186e4a32 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Tue, 10 Mar 2026 10:53:22 +0530 Subject: [PATCH 1/5] feat: ternary syntax --- src/ast/expr.rs | 6 + src/codegen/sb3.rs | 1 + src/parser/grammar.lalrpop | 2 + src/visitor.rs | 1 + src/visitor/pass0.rs | 4 + src/visitor/pass1.rs | 1 + src/visitor/pass2.rs | 1 + src/visitor/pass3.rs | 1 + src/visitor/ternary.rs | 264 +++++++++++++++++++++++++++++++++++++ 9 files changed, 281 insertions(+) create mode 100644 src/visitor/ternary.rs diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 61dd504d..993e18f5 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -65,6 +65,11 @@ pub enum Expr { property: SmolStr, span: Span, }, + Ternary { + condition: Box, + tvalue: Box, + fvalue: Box, + }, } impl Expr { @@ -80,6 +85,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 b552946c..e2c6022b 100644 --- a/src/codegen/sb3.rs +++ b/src/codegen/sb3.rs @@ -1354,6 +1354,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/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index 85a0a39d..af3de2de 100644 --- a/src/parser/grammar.lalrpop +++ b/src/parser/grammar.lalrpop @@ -481,6 +481,8 @@ IfExpr: Expr = { AND => BinOp::And .to_expr(l..r, lhs, rhs), #[precedence(level="9")] #[assoc(side="left")] OR => BinOp::Or .to_expr(l..r, lhs, rhs), + #[precedence(level="10")] #[assoc(side="left")] + IF "(" ")" ELSE => Expr::Ternary { condition: Box::new(condition), tvalue: Box::new(tvalue), fvalue: Box::new(fvalue) }, } Term: Expr = { diff --git a/src/visitor.rs b/src/visitor.rs index 9d827e7c..5a385dd1 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -3,4 +3,5 @@ pub mod pass1; pub mod pass2; pub mod pass3; pub mod pass4; +mod ternary; mod transformations; diff --git a/src/visitor/pass0.rs b/src/visitor/pass0.rs index 2e7ce5af..6ba96bfe 100644 --- a/src/visitor/pass0.rs +++ b/src/visitor/pass0.rs @@ -6,6 +6,7 @@ use glob::glob; use crate::{ ast::*, misc::SmolStr, + visitor::ternary::extract_ternary_from_stmts, }; struct V<'a> { @@ -32,6 +33,7 @@ fn visit_sprite(input: &Path, sprite: &mut Sprite, mut stage: Option<&mut Sprite .proc_locals .insert(proc.name.clone(), Default::default()); let proc_definition = sprite.proc_definitions.get_mut(&proc.name).unwrap(); + extract_ternary_from_stmts(proc_definition); visit_stmts( proc_definition, &mut V { @@ -60,6 +62,7 @@ fn visit_sprite(input: &Path, sprite: &mut Sprite, mut stage: Option<&mut Sprite ); } let func_definition = sprite.func_definitions.get_mut(&func.name).unwrap(); + extract_ternary_from_stmts(func_definition); visit_stmts( func_definition, &mut V { @@ -70,6 +73,7 @@ fn visit_sprite(input: &Path, sprite: &mut Sprite, mut stage: Option<&mut Sprite ); } for event in &mut sprite.events { + extract_ternary_from_stmts(&mut event.body); visit_stmts( &mut event.body, &mut V { 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 468f681c..6d427616 100644 --- a/src/visitor/pass2.rs +++ b/src/visitor/pass2.rs @@ -377,6 +377,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, transformations::minus); transformations::apply(expr, transformations::less_than_equal); 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..dea0c7a7 --- /dev/null +++ b/src/visitor/ternary.rs @@ -0,0 +1,264 @@ +use crate::ast::{ + Expr, + Stmt, +}; + +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 +} From 7cf514dfbb05198cfbc1acfd8b1929ae2b569a18 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Tue, 10 Mar 2026 11:23:19 +0530 Subject: [PATCH 2/5] fix: add ternary syntax to Expr rule --- src/parser/grammar.lalrpop | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index af3de2de..1556bf2d 100644 --- a/src/parser/grammar.lalrpop +++ b/src/parser/grammar.lalrpop @@ -425,6 +425,8 @@ Expr: Expr = { AND => BinOp::And .to_expr(l..r, lhs, rhs), #[precedence(level="9")] #[assoc(side="left")] OR => BinOp::Or .to_expr(l..r, lhs, rhs), + #[precedence(level="10")] #[assoc(side="left")] + IF "(" ")" ELSE => Expr::Ternary { condition: Box::new(condition), tvalue: Box::new(tvalue), fvalue: Box::new(fvalue) }, } IfExpr: Expr = { From 86c742da07ef6ae2661102abaf7acc1795e1a18d Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Tue, 10 Mar 2026 14:57:41 +0530 Subject: [PATCH 3/5] fix: no need for parens when chaining ternary --- src/parser/grammar.lalrpop | 201 +++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 97 deletions(-) diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index 1556bf2d..faea47a5 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; }, SET_LAYER_ORDER => { @@ -370,121 +370,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), - #[precedence(level="10")] #[assoc(side="left")] - IF "(" ")" ELSE => Expr::Ternary { condition: Box::new(condition), tvalue: Box::new(tvalue), fvalue: Box::new(fvalue) }, + 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), - #[precedence(level="10")] #[assoc(side="left")] - IF "(" ")" ELSE => Expr::Ternary { condition: Box::new(condition), tvalue: Box::new(tvalue), fvalue: Box::new(fvalue) }, + OR => BinOp::Or .to_expr(l..r, lhs, rhs), } Term: Expr = { @@ -497,8 +504,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()) { @@ -528,7 +535,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), @@ -540,7 +547,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, @@ -695,8 +702,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, SET_LAYER_ORDER => Token::SetLayerOrder, VAR => Token::Var, From d60f2a210104cf0c12f9078245a9e5df8e509084 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Tue, 10 Mar 2026 20:41:14 +0530 Subject: [PATCH 4/5] docs: document ternary expression --- docs/language/control-flow.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) 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. From 5886f21f844ffaf6691de27dc9b7b0c7d06bea14 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Wed, 18 Mar 2026 09:00:45 +0530 Subject: [PATCH 5/5] fix: visit ternary expressions --- src/frontend/build.rs | 3 ++- src/visitor.rs | 14 +++++++------- src/visitor/ternary.rs | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) 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/visitor.rs b/src/visitor.rs index 5a385dd1..6583ca98 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -1,7 +1,7 @@ -pub mod pass0; -pub mod pass1; -pub mod pass2; -pub mod pass3; -pub mod pass4; -mod ternary; -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/ternary.rs b/src/visitor/ternary.rs index dea0c7a7..5611be40 100644 --- a/src/visitor/ternary.rs +++ b/src/visitor/ternary.rs @@ -1,8 +1,28 @@ 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);