1use smol_str::SmolStr;
13use std::collections::{BTreeSet, HashSet, VecDeque};
14use std::rc::{Rc, Weak};
15
16use crate::CompilerConfiguration;
17use crate::expression_tree::{BindingExpression, Expression};
18use crate::langtype::{BuiltinStruct, ElementType, StructName};
19use crate::namedreference::NamedReference;
20use crate::object_tree::{Component, Document, ElementRc};
21
22pub mod accessor_names;
23
24#[cfg(feature = "cpp")]
25pub mod cpp;
26#[cfg(feature = "cpp")]
27pub mod cpp_live_preview;
28#[cfg(feature = "rust")]
29pub mod rust;
30#[cfg(feature = "rust")]
31pub mod rust_live_preview;
32#[cfg(feature = "slint-sc")]
33pub mod slint_sc;
34
35#[cfg(feature = "python")]
36pub mod python;
37
38#[derive(Clone, Debug, PartialEq)]
39pub enum OutputFormat {
40 #[cfg(feature = "cpp")]
41 Cpp(cpp::Config),
42 #[cfg(feature = "rust")]
43 Rust,
44 #[cfg(feature = "slint-sc")]
47 SlintSc,
48 Interpreter,
49 Llr,
50 #[cfg(feature = "python")]
51 Python,
52}
53
54impl OutputFormat {
55 pub fn guess_from_extension(path: &std::path::Path) -> Option<Self> {
56 match path.extension().and_then(|ext| ext.to_str()) {
57 #[cfg(feature = "cpp")]
58 Some("cpp") | Some("cxx") | Some("h") | Some("hpp") => {
59 Some(Self::Cpp(cpp::Config::default()))
60 }
61 #[cfg(feature = "rust")]
62 Some("rs") => Some(Self::Rust),
63 #[cfg(feature = "python")]
64 Some("py") => Some(Self::Python),
65 _ => None,
66 }
67 }
68}
69
70impl std::str::FromStr for OutputFormat {
71 type Err = String;
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 match s {
74 #[cfg(feature = "cpp")]
75 "cpp" => Ok(Self::Cpp(cpp::Config::default())),
76 #[cfg(feature = "rust")]
77 "rust" => Ok(Self::Rust),
78 #[cfg(feature = "slint-sc")]
79 "slint-sc" | "rust-sc" => Ok(Self::SlintSc),
80 "llr" => Ok(Self::Llr),
81 #[cfg(feature = "python")]
82 "python" => Ok(Self::Python),
83 _ => Err(format!("Unknown output format {s}")),
84 }
85 }
86}
87
88pub fn generate(
89 format: OutputFormat,
90 destination: &mut impl std::io::Write,
91 destination_path: Option<&std::path::Path>,
92 doc: &Document,
93 compiler_config: &CompilerConfiguration,
94) -> std::io::Result<()> {
95 #![allow(unused_variables)]
96 #![allow(unreachable_code)]
97
98 match format {
99 #[cfg(feature = "cpp")]
100 OutputFormat::Cpp(config) => {
101 let output = cpp::generate(doc, config, compiler_config)?;
102 write!(destination, "{output}")?;
103 }
104 #[cfg(feature = "rust")]
105 OutputFormat::Rust => {
106 let output = rust::generate(doc, compiler_config)?;
107 write!(destination, "{output}")?;
108 }
109 #[cfg(feature = "slint-sc")]
110 OutputFormat::SlintSc => {
111 let generated = slint_sc::generate(doc, compiler_config)?;
112 write!(destination, "{}", generated.code)?;
113 if let (true, Some(path)) = (compiler_config.coverage, destination_path) {
114 let map = path.with_extension("slintcov");
115 crate::fileaccess::write_file_if_changed(&map, generated.coverage_map.as_bytes())?;
116 }
117 }
118 OutputFormat::Interpreter => {
119 return Err(std::io::Error::other(
120 "Unsupported output format: The interpreter is not a valid output format yet.",
121 )); }
123 OutputFormat::Llr => {
124 let root = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
125 let mut output = String::new();
126 crate::llr::pretty_print::pretty_print(&root, &mut output).unwrap();
127 write!(destination, "{output}")?;
128 }
129 #[cfg(feature = "python")]
130 OutputFormat::Python => {
131 let output = python::generate(doc, compiler_config, destination_path)?;
132 write!(destination, "{output}")?;
133 }
134 }
135 Ok(())
136}
137
138pub trait ItemTreeBuilder {
141 type SubComponentState: Clone;
143
144 fn push_repeated_item(
145 &mut self,
146 item: &crate::object_tree::ElementRc,
147 repeater_count: u32,
148 parent_index: u32,
149 component_state: &Self::SubComponentState,
150 );
151 fn push_native_item(
152 &mut self,
153 item: &ElementRc,
154 children_offset: u32,
155 parent_index: u32,
156 component_state: &Self::SubComponentState,
157 );
158 fn enter_component(
161 &mut self,
162 item: &ElementRc,
163 sub_component: &Rc<Component>,
164 children_offset: u32,
165 component_state: &Self::SubComponentState,
166 ) -> Self::SubComponentState;
167 fn enter_component_children(
169 &mut self,
170 item: &ElementRc,
171 repeater_count: u32,
172 component_state: &Self::SubComponentState,
173 sub_component_state: &Self::SubComponentState,
174 );
175}
176
177pub fn build_item_tree<T: ItemTreeBuilder>(
179 root_component: &Rc<Component>,
180 initial_state: &T::SubComponentState,
181 builder: &mut T,
182) {
183 if let Some(sub_component) = root_component.root_element.borrow().sub_component() {
184 assert!(root_component.root_element.borrow().children.is_empty());
185 let sub_compo_state =
186 builder.enter_component(&root_component.root_element, sub_component, 1, initial_state);
187 builder.enter_component_children(
188 &root_component.root_element,
189 0,
190 initial_state,
191 &sub_compo_state,
192 );
193 build_item_tree::<T>(sub_component, &sub_compo_state, builder);
194 } else {
195 let mut repeater_count = 0;
196 visit_item(initial_state, &root_component.root_element, 1, &mut repeater_count, 0, builder);
197
198 visit_children(
199 initial_state,
200 &root_component.root_element.borrow().children,
201 root_component,
202 &root_component.root_element,
203 0,
204 0,
205 1,
206 1,
207 &mut repeater_count,
208 builder,
209 );
210 }
211
212 fn item_sub_tree_size(e: &ElementRc) -> usize {
216 let mut count = e.borrow().children.len();
217 if let Some(sub_component) = e.borrow().sub_component() {
218 count += item_sub_tree_size(&sub_component.root_element);
219 }
220 for i in &e.borrow().children {
221 count += item_sub_tree_size(i);
222 }
223 count
224 }
225
226 fn visit_children<T: ItemTreeBuilder>(
227 state: &T::SubComponentState,
228 children: &[ElementRc],
229 _component: &Rc<Component>,
230 parent_item: &ElementRc,
231 parent_index: u32,
232 relative_parent_index: u32,
233 children_offset: u32,
234 relative_children_offset: u32,
235 repeater_count: &mut u32,
236 builder: &mut T,
237 ) {
238 debug_assert_eq!(
239 relative_parent_index,
240 *parent_item.borrow().item_index.get().unwrap_or(&parent_index)
241 );
242
243 if children.is_empty()
257 && let Some(nested_subcomponent) = parent_item.borrow().sub_component()
258 {
259 let sub_component_state =
260 builder.enter_component(parent_item, nested_subcomponent, children_offset, state);
261 visit_children(
262 &sub_component_state,
263 &nested_subcomponent.root_element.borrow().children,
264 nested_subcomponent,
265 &nested_subcomponent.root_element,
266 parent_index,
267 relative_parent_index,
268 children_offset,
269 relative_children_offset,
270 repeater_count,
271 builder,
272 );
273 return;
274 }
275
276 let mut offset = children_offset + children.len() as u32;
277
278 let mut sub_component_states = VecDeque::new();
279
280 for child in children.iter() {
281 if let Some(sub_component) = child.borrow().sub_component() {
282 let sub_component_state =
283 builder.enter_component(child, sub_component, offset, state);
284 visit_item(
285 &sub_component_state,
286 &sub_component.root_element,
287 offset,
288 repeater_count,
289 parent_index,
290 builder,
291 );
292 sub_component_states.push_back(sub_component_state);
293 } else {
294 visit_item(state, child, offset, repeater_count, parent_index, builder);
295 }
296 offset += item_sub_tree_size(child) as u32;
297 }
298
299 let mut offset = children_offset + children.len() as u32;
300 let mut relative_offset = relative_children_offset + children.len() as u32;
301
302 for (i, e) in children.iter().enumerate() {
303 let index = children_offset + i as u32;
304 let relative_index = relative_children_offset + i as u32;
305 if let Some(sub_component) = e.borrow().sub_component() {
306 let sub_tree_state = sub_component_states.pop_front().unwrap();
307 builder.enter_component_children(e, *repeater_count, state, &sub_tree_state);
308 visit_children(
309 &sub_tree_state,
310 &sub_component.root_element.borrow().children,
311 sub_component,
312 &sub_component.root_element,
313 index,
314 0,
315 offset,
316 1,
317 repeater_count,
318 builder,
319 );
320 } else {
321 visit_children(
322 state,
323 &e.borrow().children,
324 _component,
325 e,
326 index,
327 relative_index,
328 offset,
329 relative_offset,
330 repeater_count,
331 builder,
332 );
333 }
334
335 let size = item_sub_tree_size(e) as u32;
336 offset += size;
337 relative_offset += size;
338 }
339 }
340
341 fn visit_item<T: ItemTreeBuilder>(
342 component_state: &T::SubComponentState,
343 item: &ElementRc,
344 children_offset: u32,
345 repeater_count: &mut u32,
346 parent_index: u32,
347 builder: &mut T,
348 ) {
349 if item.borrow().repeated.is_some() {
350 builder.push_repeated_item(item, *repeater_count, parent_index, component_state);
351 *repeater_count += 1;
352 } else {
353 let mut item = item.clone();
354 let mut component_state = component_state.clone();
355 while let Some((base, state)) = {
356 item.borrow().sub_component().map(|c| {
357 (
358 c.root_element.clone(),
359 builder.enter_component(&item, c, children_offset, &component_state),
360 )
361 })
362 } {
363 item = base;
364 component_state = state;
365 }
366 builder.push_native_item(&item, children_offset, parent_index, &component_state)
367 }
368 }
369}
370
371pub fn handle_property_bindings_init(
375 component: &Rc<Component>,
376 mut handle_property: impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
377) {
378 fn handle_property_inner(
379 component: &Weak<Component>,
380 elem: &ElementRc,
381 prop_name: &SmolStr,
382 binding_expression: &BindingExpression,
383 handle_property: &mut impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
384 processed: &mut HashSet<NamedReference>,
385 ) {
386 if elem.borrow().is_component_placeholder {
387 return; }
389 let nr = NamedReference::new(elem, prop_name.clone());
390 if processed.contains(&nr) {
391 return;
392 }
393 processed.insert(nr);
394 if binding_expression.analysis.as_ref().is_some_and(|a| a.is_const) {
395 binding_expression.expression.visit_recursive(&mut |e| {
398 if let Expression::PropertyReference(nr) = e {
399 let elem = nr.element();
400 if Weak::ptr_eq(&elem.borrow().enclosing_component, component)
401 && let Some(be) = elem.borrow().binding_cell_including_synthetic(nr.name())
402 {
403 handle_property_inner(
404 component,
405 &elem,
406 nr.name(),
407 &be.borrow(),
408 handle_property,
409 processed,
410 );
411 }
412 }
413 })
414 }
415 handle_property(elem, prop_name, binding_expression);
416 }
417
418 let mut processed = HashSet::new();
419 crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
420 for (prop_name, binding_expression) in elem.borrow().bindings_including_synthetic() {
421 handle_property_inner(
422 &Rc::downgrade(component),
423 elem,
424 prop_name,
425 &binding_expression.borrow(),
426 &mut handle_property,
427 &mut processed,
428 );
429 }
430 });
431}
432
433pub fn for_each_const_properties(
436 component: &Rc<Component>,
437 mut f: impl FnMut(&ElementRc, &SmolStr),
438) {
439 crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
440 if elem.borrow().repeated.is_some() {
441 return;
442 }
443 let mut e = elem.clone();
444 let mut all_prop = BTreeSet::new();
445 loop {
446 all_prop.extend(
447 e.borrow()
448 .property_declarations
449 .iter()
450 .filter(|(_, x)| {
451 x.property_type.is_property_type() &&
452 !matches!( &x.property_type, crate::langtype::Type::Struct(s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo)))
453 })
454 .map(|(k, _)| k.clone()),
455 );
456 match &e.clone().borrow().base_type {
457 ElementType::Component(c) => {
458 e = c.root_element.clone();
459 }
460 ElementType::Native(n) => {
461 let mut n = n;
462 loop {
463 all_prop.extend(
464 n.properties
465 .iter()
466 .filter(|(k, x)| {
467 x.ty.is_property_type()
468 && (n.class_name != "Flickable"
469 || !k.starts_with("content-"))
470 && k.as_str() != "commands"
471 })
472 .map(|(k, _)| k.clone()),
473 );
474 match n.parent.as_ref() {
475 Some(p) => n = p,
476 None => break,
477 }
478 }
479 break;
480 }
481 ElementType::Builtin(_) => {
482 unreachable!("builtin element should have been resolved")
483 }
484 ElementType::Global | ElementType::Interface | ElementType::Error => break,
485 }
486 }
487 for c in all_prop {
488 if NamedReference::new(elem, c.clone()).is_constant() {
489 f(elem, &c);
490 }
491 }
492 });
493}
494
495pub fn to_pascal_case(str: &str) -> String {
497 let mut result = Vec::with_capacity(str.len());
498 let mut next_upper = true;
499 for x in str.as_bytes() {
500 if *x == b'-' {
501 next_upper = true;
502 } else if next_upper {
503 result.push(x.to_ascii_uppercase());
504 next_upper = false;
505 } else {
506 result.push(*x);
507 }
508 }
509 String::from_utf8(result).unwrap()
510}
511
512pub fn to_kebab_case(str: &str) -> String {
514 let mut result = Vec::with_capacity(str.len());
515 for x in str.as_bytes() {
516 if x.is_ascii_uppercase() {
517 if !result.is_empty() {
518 result.push(b'-');
519 }
520 result.push(x.to_ascii_lowercase());
521 } else {
522 result.push(*x);
523 }
524 }
525 String::from_utf8(result).unwrap()
526}
527
528pub fn accessibility_action_argument_count(action: &str) -> usize {
535 let property_name = format!("accessible-action-{}", to_kebab_case(action));
536 crate::typeregister::reserved_accessibility_properties()
537 .find_map(|(name, ty)| match ty {
538 crate::langtype::Type::Callback(function) if name == property_name => {
539 Some(function.args.len())
540 }
541 _ => None,
542 })
543 .unwrap_or_else(|| panic!("Unknown accessibility action {action}"))
544}
545
546#[test]
547fn case_conversions() {
548 assert_eq!(to_kebab_case("HelloWorld"), "hello-world");
549 assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
550}