| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use std::fmt::{Display, Result, Write}; |
| 5 | |
| 6 | use itertools::Itertools; |
| 7 | |
| 8 | use crate::expression_tree::MinMaxOp; |
| 9 | |
| 10 | use super::{ |
| 11 | CompilationUnit, EvaluationContext, Expression, ParentCtx, PropertyReference, SubComponentIdx, |
| 12 | }; |
| 13 | |
| 14 | pub fn pretty_print(root: &CompilationUnit, writer: &mut dyn Write) -> Result { |
| 15 | PrettyPrinter { writer, indentation: 0 }.print_root(root) |
| 16 | } |
| 17 | |
| 18 | struct PrettyPrinter<'a> { |
| 19 | writer: &'a mut dyn Write, |
| 20 | indentation: usize, |
| 21 | } |
| 22 | |
| 23 | impl PrettyPrinter<'_> { |
| 24 | fn print_root(&mut self, root: &CompilationUnit) -> Result { |
| 25 | for (idx, g) in root.globals.iter_enumerated() { |
| 26 | if !g.is_builtin { |
| 27 | self.print_global(root, idx, g)?; |
| 28 | } |
| 29 | } |
| 30 | for c in root.sub_components.keys() { |
| 31 | self.print_component(root, c, None)? |
| 32 | } |
| 33 | |
| 34 | Ok(()) |
| 35 | } |
| 36 | |
| 37 | fn print_component( |
| 38 | &mut self, |
| 39 | root: &CompilationUnit, |
| 40 | sc_idx: SubComponentIdx, |
| 41 | parent: Option<ParentCtx<'_>>, |
| 42 | ) -> Result { |
| 43 | let ctx = EvaluationContext::new_sub_component(root, sc_idx, (), parent); |
| 44 | let sc = &root.sub_components[sc_idx]; |
| 45 | writeln!(self.writer, "component {} {{" , sc.name)?; |
| 46 | self.indentation += 1; |
| 47 | for p in &sc.properties { |
| 48 | self.indent()?; |
| 49 | writeln!(self.writer, "property < {}> {}; //use= {}" , p.ty, p.name, p.use_count.get())?; |
| 50 | } |
| 51 | for f in &sc.functions { |
| 52 | self.indent()?; |
| 53 | writeln!( |
| 54 | self.writer, |
| 55 | "function {} ( {}) -> {} {{ {} }}; " , |
| 56 | f.name, |
| 57 | f.args.iter().map(ToString::to_string).join(", " ), |
| 58 | f.ret_ty, |
| 59 | DisplayExpression(&f.code, &ctx) |
| 60 | )?; |
| 61 | } |
| 62 | for (p1, p2) in &sc.two_way_bindings { |
| 63 | self.indent()?; |
| 64 | writeln!( |
| 65 | self.writer, |
| 66 | " {} <=> {};" , |
| 67 | DisplayPropertyRef(p1, &ctx), |
| 68 | DisplayPropertyRef(p2, &ctx) |
| 69 | )? |
| 70 | } |
| 71 | for (p, init) in &sc.property_init { |
| 72 | self.indent()?; |
| 73 | writeln!( |
| 74 | self.writer, |
| 75 | " {}: {}; {}" , |
| 76 | DisplayPropertyRef(p, &ctx), |
| 77 | DisplayExpression(&init.expression.borrow(), &ctx), |
| 78 | if init.is_constant { " /*const*/" } else { "" } |
| 79 | )? |
| 80 | } |
| 81 | for (p, e) in &sc.change_callbacks { |
| 82 | self.indent()?; |
| 83 | writeln!( |
| 84 | self.writer, |
| 85 | "changed {} => {};" , |
| 86 | DisplayPropertyRef(p, &ctx), |
| 87 | DisplayExpression(&e.borrow(), &ctx), |
| 88 | )? |
| 89 | } |
| 90 | for ssc in &sc.sub_components { |
| 91 | self.indent()?; |
| 92 | writeln!(self.writer, " {} := {} {{}};" , ssc.name, root.sub_components[ssc.ty].name)?; |
| 93 | } |
| 94 | for (item, geom) in std::iter::zip(&sc.items, &sc.geometries) { |
| 95 | self.indent()?; |
| 96 | let geometry = geom.as_ref().map_or(String::new(), |geom| { |
| 97 | format!("geometry: {}" , DisplayExpression(&geom.borrow(), &ctx)) |
| 98 | }); |
| 99 | writeln!(self.writer, " {} := {} {{ {geometry} }};" , item.name, item.ty.class_name)?; |
| 100 | } |
| 101 | for (idx, r) in sc.repeated.iter_enumerated() { |
| 102 | self.indent()?; |
| 103 | write!(self.writer, "for in {} : " , DisplayExpression(&r.model.borrow(), &ctx))?; |
| 104 | self.print_component(root, r.sub_tree.root, Some(ParentCtx::new(&ctx, Some(idx))))? |
| 105 | } |
| 106 | for t in &sc.menu_item_trees { |
| 107 | self.indent()?; |
| 108 | self.print_component(root, t.root, Some(ParentCtx::new(&ctx, None)))? |
| 109 | } |
| 110 | for w in &sc.popup_windows { |
| 111 | self.indent()?; |
| 112 | self.print_component(root, w.item_tree.root, Some(ParentCtx::new(&ctx, None)))? |
| 113 | } |
| 114 | self.indentation -= 1; |
| 115 | self.indent()?; |
| 116 | writeln!(self.writer, " }}" ) |
| 117 | } |
| 118 | |
| 119 | fn print_global( |
| 120 | &mut self, |
| 121 | root: &CompilationUnit, |
| 122 | idx: super::GlobalIdx, |
| 123 | global: &super::GlobalComponent, |
| 124 | ) -> Result { |
| 125 | let ctx = EvaluationContext::new_global(root, idx, ()); |
| 126 | if global.exported { |
| 127 | write!(self.writer, "export " )?; |
| 128 | } |
| 129 | let aliases = global.aliases.join("," ); |
| 130 | let aliases = if aliases.is_empty() { String::new() } else { format!(" /* {aliases}*/" ) }; |
| 131 | writeln!(self.writer, "global {} {{{aliases}" , global.name)?; |
| 132 | self.indentation += 1; |
| 133 | for ((p, init), is_const) in |
| 134 | std::iter::zip(&global.properties, &global.init_values).zip(&global.const_properties) |
| 135 | { |
| 136 | self.indent()?; |
| 137 | let init = init.as_ref().map_or(String::new(), |init| { |
| 138 | format!( |
| 139 | ": {}{}" , |
| 140 | DisplayExpression(&init.expression.borrow(), &ctx,), |
| 141 | if init.is_constant { "/*const*/" } else { "" } |
| 142 | ) |
| 143 | }); |
| 144 | writeln!( |
| 145 | self.writer, |
| 146 | "property < {}> {}{init}; //use= {}{}" , |
| 147 | p.ty, |
| 148 | p.name, |
| 149 | p.use_count.get(), |
| 150 | if *is_const { " const" } else { "" } |
| 151 | )?; |
| 152 | } |
| 153 | for (p, e) in &global.change_callbacks { |
| 154 | self.indent()?; |
| 155 | writeln!( |
| 156 | self.writer, |
| 157 | "changed {} => {};" , |
| 158 | global.properties[*p].name, |
| 159 | DisplayExpression(&e.borrow(), &ctx), |
| 160 | )? |
| 161 | } |
| 162 | for f in &global.functions { |
| 163 | self.indent()?; |
| 164 | writeln!( |
| 165 | self.writer, |
| 166 | "function {} ( {}) -> {} {{ {} }}; " , |
| 167 | f.name, |
| 168 | f.args.iter().map(ToString::to_string).join(", " ), |
| 169 | f.ret_ty, |
| 170 | DisplayExpression(&f.code, &ctx) |
| 171 | )?; |
| 172 | } |
| 173 | self.indentation -= 1; |
| 174 | self.indent()?; |
| 175 | writeln!(self.writer, " }}" ) |
| 176 | } |
| 177 | |
| 178 | fn indent(&mut self) -> Result { |
| 179 | for _ in 0..self.indentation { |
| 180 | self.writer.write_str(" " )?; |
| 181 | } |
| 182 | Ok(()) |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | pub struct DisplayPropertyRef<'a, T>(pub &'a PropertyReference, pub &'a EvaluationContext<'a, T>); |
| 187 | impl<T> Display for DisplayPropertyRef<'_, T> { |
| 188 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { |
| 189 | let mut ctx = self.1; |
| 190 | match &self.0 { |
| 191 | PropertyReference::Local { sub_component_path, property_index } => { |
| 192 | if let Some(g) = ctx.current_global() { |
| 193 | write!(f, " {}. {}" , g.name, g.properties[*property_index].name) |
| 194 | } else { |
| 195 | let mut sc = ctx.current_sub_component().unwrap(); |
| 196 | for i in sub_component_path { |
| 197 | write!(f, " {}." , sc.sub_components[*i].name)?; |
| 198 | sc = &ctx.compilation_unit.sub_components[sc.sub_components[*i].ty]; |
| 199 | } |
| 200 | write!(f, " {}" , sc.properties[*property_index].name) |
| 201 | } |
| 202 | } |
| 203 | PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => { |
| 204 | let mut sc = ctx.current_sub_component().unwrap(); |
| 205 | for i in sub_component_path { |
| 206 | write!(f, " {}." , sc.sub_components[*i].name)?; |
| 207 | sc = &ctx.compilation_unit.sub_components[sc.sub_components[*i].ty]; |
| 208 | } |
| 209 | let i = &sc.items[*item_index]; |
| 210 | write!(f, " {}. {}" , i.name, prop_name) |
| 211 | } |
| 212 | PropertyReference::InParent { level, parent_reference } => { |
| 213 | for _ in 0..level.get() { |
| 214 | if ctx.parent.is_none() { |
| 215 | return write!(f, "<invalid parent reference>" ); |
| 216 | } |
| 217 | ctx = ctx.parent.unwrap().ctx; |
| 218 | } |
| 219 | write!(f, " {}" , Self(parent_reference, ctx)) |
| 220 | } |
| 221 | PropertyReference::Global { global_index, property_index } => { |
| 222 | let g = &ctx.compilation_unit.globals[*global_index]; |
| 223 | write!(f, " {}. {}" , g.name, g.properties[*property_index].name) |
| 224 | } |
| 225 | PropertyReference::Function { sub_component_path, function_index } => { |
| 226 | if let Some(g) = ctx.current_global() { |
| 227 | write!(f, " {}. {}" , g.name, g.functions[*function_index].name) |
| 228 | } else { |
| 229 | let mut sc = ctx.current_sub_component().unwrap(); |
| 230 | for i in sub_component_path { |
| 231 | write!(f, " {}." , sc.sub_components[*i].name)?; |
| 232 | sc = &ctx.compilation_unit.sub_components[sc.sub_components[*i].ty]; |
| 233 | } |
| 234 | write!(f, " {}" , sc.functions[*function_index].name) |
| 235 | } |
| 236 | } |
| 237 | PropertyReference::GlobalFunction { global_index, function_index } => { |
| 238 | let g = &ctx.compilation_unit.globals[*global_index]; |
| 239 | write!(f, " {}. {}" , g.name, g.functions[*function_index].name) |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | pub struct DisplayExpression<'a, T>(pub &'a Expression, pub &'a EvaluationContext<'a, T>); |
| 246 | impl<'a, T> Display for DisplayExpression<'a, T> { |
| 247 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { |
| 248 | let ctx = self.1; |
| 249 | let e = |e: &'a Expression| DisplayExpression(e, ctx); |
| 250 | match self.0 { |
| 251 | Expression::StringLiteral(x) => write!(f, " {x:?}" ), |
| 252 | Expression::NumberLiteral(x) => write!(f, " {x:?}" ), |
| 253 | Expression::BoolLiteral(x) => write!(f, " {x:?}" ), |
| 254 | Expression::PropertyReference(x) => write!(f, " {}" , DisplayPropertyRef(x, ctx)), |
| 255 | Expression::FunctionParameterReference { index } => write!(f, "arg_ {index}" ), |
| 256 | Expression::StoreLocalVariable { name, value } => { |
| 257 | write!(f, " {} = {}" , name, e(value)) |
| 258 | } |
| 259 | Expression::ReadLocalVariable { name, .. } => write!(f, " {name}" ), |
| 260 | Expression::StructFieldAccess { base, name } => write!(f, " {}. {}" , e(base), name), |
| 261 | Expression::ArrayIndex { array, index } => write!(f, " {}[ {}]" , e(array), e(index)), |
| 262 | Expression::Cast { from, to } => write!(f, " {} /*as {:?}*/" , e(from), to), |
| 263 | Expression::CodeBlock(v) => { |
| 264 | write!(f, " {{ {} }}" , v.iter().map(e).join("; " )) |
| 265 | } |
| 266 | Expression::BuiltinFunctionCall { function, arguments } => { |
| 267 | write!(f, " {:?}( {})" , function, arguments.iter().map(e).join(", " )) |
| 268 | } |
| 269 | Expression::CallBackCall { callback, arguments } => { |
| 270 | write!( |
| 271 | f, |
| 272 | " {}( {})" , |
| 273 | DisplayPropertyRef(callback, ctx), |
| 274 | arguments.iter().map(e).join(", " ) |
| 275 | ) |
| 276 | } |
| 277 | Expression::FunctionCall { function, arguments } => { |
| 278 | write!( |
| 279 | f, |
| 280 | " {}( {})" , |
| 281 | DisplayPropertyRef(function, ctx), |
| 282 | arguments.iter().map(e).join(", " ) |
| 283 | ) |
| 284 | } |
| 285 | Expression::ItemMemberFunctionCall { function } => { |
| 286 | write!(f, " {}()" , DisplayPropertyRef(function, ctx)) |
| 287 | } |
| 288 | Expression::ExtraBuiltinFunctionCall { function, arguments, .. } => { |
| 289 | write!(f, " {}( {})" , function, arguments.iter().map(e).join(", " )) |
| 290 | } |
| 291 | Expression::PropertyAssignment { property, value } => { |
| 292 | write!(f, " {} = {}" , DisplayPropertyRef(property, ctx), e(value)) |
| 293 | } |
| 294 | Expression::ModelDataAssignment { level, value } => { |
| 295 | write!(f, "data_ {} = {}" , level, e(value)) |
| 296 | } |
| 297 | Expression::ArrayIndexAssignment { array, index, value } => { |
| 298 | write!(f, " {}[ {}] = {}" , e(array), e(index), e(value)) |
| 299 | } |
| 300 | Expression::BinaryExpression { lhs, rhs, op } => { |
| 301 | write!(f, "( {} {} {})" , e(lhs), op, e(rhs)) |
| 302 | } |
| 303 | Expression::UnaryOp { sub, op } => write!(f, " {}{}" , op, e(sub)), |
| 304 | Expression::ImageReference { resource_ref, nine_slice } => { |
| 305 | write!(f, " {resource_ref:?}" )?; |
| 306 | if let Some(nine_slice) = &nine_slice { |
| 307 | write!(f, "nine-slice( {nine_slice:?})" )?; |
| 308 | } |
| 309 | Ok(()) |
| 310 | } |
| 311 | Expression::Condition { condition, true_expr, false_expr } => { |
| 312 | write!(f, "( {} ? {} : {})" , e(condition), e(true_expr), e(false_expr)) |
| 313 | } |
| 314 | Expression::Array { values, .. } => { |
| 315 | write!(f, "[ {}]" , values.iter().map(e).join(", " )) |
| 316 | } |
| 317 | Expression::Struct { values, .. } => write!( |
| 318 | f, |
| 319 | " {{ {} }}" , |
| 320 | values.iter().map(|(k, v)| format!(" {}: {}" , k, e(v))).join(", " ) |
| 321 | ), |
| 322 | Expression::EasingCurve(x) => write!(f, " {x:?}" ), |
| 323 | Expression::LinearGradient { angle, stops } => write!( |
| 324 | f, |
| 325 | "@linear-gradient( {}, {})" , |
| 326 | e(angle), |
| 327 | stops.iter().map(|(e1, e2)| format!(" {} {}" , e(e1), e(e2))).join(", " ) |
| 328 | ), |
| 329 | Expression::RadialGradient { stops } => write!( |
| 330 | f, |
| 331 | "@radial-gradient(circle, {})" , |
| 332 | stops.iter().map(|(e1, e2)| format!(" {} {}" , e(e1), e(e2))).join(", " ) |
| 333 | ), |
| 334 | Expression::EnumerationValue(x) => write!(f, " {x}" ), |
| 335 | Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index: None } => { |
| 336 | write!(f, " {}[ {}]" , DisplayPropertyRef(layout_cache_prop, ctx), index) |
| 337 | } |
| 338 | Expression::LayoutCacheAccess { |
| 339 | layout_cache_prop, |
| 340 | index, |
| 341 | repeater_index: Some(ri), |
| 342 | } => { |
| 343 | write!(f, " {}[ {} % {}]" , DisplayPropertyRef(layout_cache_prop, ctx), index, e(ri)) |
| 344 | } |
| 345 | Expression::BoxLayoutFunction { .. } => write!(f, "BoxLayoutFunction(TODO)" ,), |
| 346 | Expression::ComputeDialogLayoutCells { .. } => { |
| 347 | write!(f, "ComputeDialogLayoutCells(TODO)" ,) |
| 348 | } |
| 349 | Expression::MinMax { ty: _, op, lhs, rhs } => match op { |
| 350 | MinMaxOp::Min => write!(f, "min( {}, {})" , e(lhs), e(rhs)), |
| 351 | MinMaxOp::Max => write!(f, "max( {}, {})" , e(lhs), e(rhs)), |
| 352 | }, |
| 353 | Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>" ,), |
| 354 | Expression::TranslationReference { format_args, string_index, plural } => { |
| 355 | match plural { |
| 356 | Some(plural) => write!( |
| 357 | f, |
| 358 | "@tr( {:?} % {}, {})" , |
| 359 | string_index, |
| 360 | DisplayExpression(plural, ctx), |
| 361 | DisplayExpression(format_args, ctx) |
| 362 | ), |
| 363 | None => write!( |
| 364 | f, |
| 365 | "@tr( {:?}, {})" , |
| 366 | string_index, |
| 367 | DisplayExpression(format_args, ctx) |
| 368 | ), |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | |