Skip to main content

i_slint_compiler/
generator.rs

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/*!
5The module responsible for the code generation.
6
7There is one sub module for every language
8*/
9
10// cSpell: ignore deque subcomponent
11
12use 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::{BuiltinPrivateStruct, ElementType, StructName};
19use crate::namedreference::NamedReference;
20use crate::object_tree::{Component, Document, ElementRc};
21
22#[cfg(feature = "cpp")]
23pub mod cpp;
24#[cfg(feature = "cpp")]
25pub mod cpp_live_preview;
26#[cfg(feature = "rust")]
27pub mod rust;
28#[cfg(feature = "rust")]
29pub mod rust_live_preview;
30
31#[cfg(feature = "python")]
32pub mod python;
33
34#[derive(Clone, Debug, PartialEq)]
35pub enum OutputFormat {
36    #[cfg(feature = "cpp")]
37    Cpp(cpp::Config),
38    #[cfg(feature = "rust")]
39    Rust,
40    Interpreter,
41    Llr,
42    #[cfg(feature = "python")]
43    Python,
44}
45
46impl OutputFormat {
47    pub fn guess_from_extension(path: &std::path::Path) -> Option<Self> {
48        match path.extension().and_then(|ext| ext.to_str()) {
49            #[cfg(feature = "cpp")]
50            Some("cpp") | Some("cxx") | Some("h") | Some("hpp") => {
51                Some(Self::Cpp(cpp::Config::default()))
52            }
53            #[cfg(feature = "rust")]
54            Some("rs") => Some(Self::Rust),
55            #[cfg(feature = "python")]
56            Some("py") => Some(Self::Python),
57            _ => None,
58        }
59    }
60}
61
62impl std::str::FromStr for OutputFormat {
63    type Err = String;
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        match s {
66            #[cfg(feature = "cpp")]
67            "cpp" => Ok(Self::Cpp(cpp::Config::default())),
68            #[cfg(feature = "rust")]
69            "rust" => Ok(Self::Rust),
70            "llr" => Ok(Self::Llr),
71            #[cfg(feature = "python")]
72            "python" => Ok(Self::Python),
73            _ => Err(format!("Unknown output format {s}")),
74        }
75    }
76}
77
78pub fn generate(
79    format: OutputFormat,
80    destination: &mut impl std::io::Write,
81    destination_path: Option<&std::path::Path>,
82    doc: &Document,
83    compiler_config: &CompilerConfiguration,
84) -> std::io::Result<()> {
85    #![allow(unused_variables)]
86    #![allow(unreachable_code)]
87
88    match format {
89        #[cfg(feature = "cpp")]
90        OutputFormat::Cpp(config) => {
91            let output = cpp::generate(doc, config, compiler_config)?;
92            write!(destination, "{output}")?;
93        }
94        #[cfg(feature = "rust")]
95        OutputFormat::Rust => {
96            let output = rust::generate(doc, compiler_config)?;
97            write!(destination, "{output}")?;
98        }
99        OutputFormat::Interpreter => {
100            return Err(std::io::Error::other(
101                "Unsupported output format: The interpreter is not a valid output format yet.",
102            )); // Perhaps byte code in the future?
103        }
104        OutputFormat::Llr => {
105            let root = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
106            let mut output = String::new();
107            crate::llr::pretty_print::pretty_print(&root, &mut output).unwrap();
108            write!(destination, "{output}")?;
109        }
110        #[cfg(feature = "python")]
111        OutputFormat::Python => {
112            let output = python::generate(doc, compiler_config, destination_path)?;
113            write!(destination, "{output}")?;
114        }
115    }
116    Ok(())
117}
118
119/// A reference to this trait is passed to the [`build_item_tree`] function.
120/// It can be used to build the array for the item tree.
121pub trait ItemTreeBuilder {
122    /// Some state that contains the code on how to access some particular component
123    type SubComponentState: Clone;
124
125    fn push_repeated_item(
126        &mut self,
127        item: &crate::object_tree::ElementRc,
128        repeater_count: u32,
129        parent_index: u32,
130        component_state: &Self::SubComponentState,
131    );
132    fn push_native_item(
133        &mut self,
134        item: &ElementRc,
135        children_offset: u32,
136        parent_index: u32,
137        component_state: &Self::SubComponentState,
138    );
139    /// Called when a component is entered, this allow to change the component_state.
140    /// The returned SubComponentState will be used for all the items within that component
141    fn enter_component(
142        &mut self,
143        item: &ElementRc,
144        sub_component: &Rc<Component>,
145        children_offset: u32,
146        component_state: &Self::SubComponentState,
147    ) -> Self::SubComponentState;
148    /// Called before the children of a component are entered.
149    fn enter_component_children(
150        &mut self,
151        item: &ElementRc,
152        repeater_count: u32,
153        component_state: &Self::SubComponentState,
154        sub_component_state: &Self::SubComponentState,
155    );
156}
157
158/// Visit each item in order in which they should appear in the children tree array.
159pub fn build_item_tree<T: ItemTreeBuilder>(
160    root_component: &Rc<Component>,
161    initial_state: &T::SubComponentState,
162    builder: &mut T,
163) {
164    if let Some(sub_component) = root_component.root_element.borrow().sub_component() {
165        assert!(root_component.root_element.borrow().children.is_empty());
166        let sub_compo_state =
167            builder.enter_component(&root_component.root_element, sub_component, 1, initial_state);
168        builder.enter_component_children(
169            &root_component.root_element,
170            0,
171            initial_state,
172            &sub_compo_state,
173        );
174        build_item_tree::<T>(sub_component, &sub_compo_state, builder);
175    } else {
176        let mut repeater_count = 0;
177        visit_item(initial_state, &root_component.root_element, 1, &mut repeater_count, 0, builder);
178
179        visit_children(
180            initial_state,
181            &root_component.root_element.borrow().children,
182            root_component,
183            &root_component.root_element,
184            0,
185            0,
186            1,
187            1,
188            &mut repeater_count,
189            builder,
190        );
191    }
192
193    // Size of the element's children and grand-children including
194    // sub-component children, needed to allocate the correct amount of
195    // index spaces for sub-components.
196    fn item_sub_tree_size(e: &ElementRc) -> usize {
197        let mut count = e.borrow().children.len();
198        if let Some(sub_component) = e.borrow().sub_component() {
199            count += item_sub_tree_size(&sub_component.root_element);
200        }
201        for i in &e.borrow().children {
202            count += item_sub_tree_size(i);
203        }
204        count
205    }
206
207    fn visit_children<T: ItemTreeBuilder>(
208        state: &T::SubComponentState,
209        children: &[ElementRc],
210        _component: &Rc<Component>,
211        parent_item: &ElementRc,
212        parent_index: u32,
213        relative_parent_index: u32,
214        children_offset: u32,
215        relative_children_offset: u32,
216        repeater_count: &mut u32,
217        builder: &mut T,
218    ) {
219        debug_assert_eq!(
220            relative_parent_index,
221            *parent_item.borrow().item_index.get().unwrap_or(&parent_index)
222        );
223
224        // Suppose we have this:
225        // ```
226        // Button := Rectangle { /* some repeater here*/ }
227        // StandardButton := Button { /* no children */ }
228        // App := Dialog { StandardButton { /* no children */ }}
229        // ```
230        // The inlining pass ensures that *if* `StandardButton` had children, `Button` would be inlined, but that's not the case here.
231        //
232        // We are in the stage of visiting the Dialog's children and we'll end up visiting the Button's Rectangle because visit_item()
233        // on the StandardButton - a Dialog's child - follows all the way to the Rectangle as native item. We've also determine that
234        // StandardButton is a sub-component and we'll call visit_children() on it. Now we are here. However as `StandardButton` has no children,
235        // and therefore we would never recurse into `Button`'s children and thus miss the repeater. That is what this condition attempts to
236        // detect and chain the children visitation.
237        if children.is_empty()
238            && let Some(nested_subcomponent) = parent_item.borrow().sub_component()
239        {
240            let sub_component_state =
241                builder.enter_component(parent_item, nested_subcomponent, children_offset, state);
242            visit_children(
243                &sub_component_state,
244                &nested_subcomponent.root_element.borrow().children,
245                nested_subcomponent,
246                &nested_subcomponent.root_element,
247                parent_index,
248                relative_parent_index,
249                children_offset,
250                relative_children_offset,
251                repeater_count,
252                builder,
253            );
254            return;
255        }
256
257        let mut offset = children_offset + children.len() as u32;
258
259        let mut sub_component_states = VecDeque::new();
260
261        for child in children.iter() {
262            if let Some(sub_component) = child.borrow().sub_component() {
263                let sub_component_state =
264                    builder.enter_component(child, sub_component, offset, state);
265                visit_item(
266                    &sub_component_state,
267                    &sub_component.root_element,
268                    offset,
269                    repeater_count,
270                    parent_index,
271                    builder,
272                );
273                sub_component_states.push_back(sub_component_state);
274            } else {
275                visit_item(state, child, offset, repeater_count, parent_index, builder);
276            }
277            offset += item_sub_tree_size(child) as u32;
278        }
279
280        let mut offset = children_offset + children.len() as u32;
281        let mut relative_offset = relative_children_offset + children.len() as u32;
282        let mut index = children_offset;
283        let mut relative_index = relative_children_offset;
284
285        for e in children.iter() {
286            if let Some(sub_component) = e.borrow().sub_component() {
287                let sub_tree_state = sub_component_states.pop_front().unwrap();
288                builder.enter_component_children(e, *repeater_count, state, &sub_tree_state);
289                visit_children(
290                    &sub_tree_state,
291                    &sub_component.root_element.borrow().children,
292                    sub_component,
293                    &sub_component.root_element,
294                    index,
295                    0,
296                    offset,
297                    1,
298                    repeater_count,
299                    builder,
300                );
301            } else {
302                visit_children(
303                    state,
304                    &e.borrow().children,
305                    _component,
306                    e,
307                    index,
308                    relative_index,
309                    offset,
310                    relative_offset,
311                    repeater_count,
312                    builder,
313                );
314            }
315
316            index += 1;
317            relative_index += 1;
318            let size = item_sub_tree_size(e) as u32;
319            offset += size;
320            relative_offset += size;
321        }
322    }
323
324    fn visit_item<T: ItemTreeBuilder>(
325        component_state: &T::SubComponentState,
326        item: &ElementRc,
327        children_offset: u32,
328        repeater_count: &mut u32,
329        parent_index: u32,
330        builder: &mut T,
331    ) {
332        if item.borrow().repeated.is_some() {
333            builder.push_repeated_item(item, *repeater_count, parent_index, component_state);
334            *repeater_count += 1;
335        } else {
336            let mut item = item.clone();
337            let mut component_state = component_state.clone();
338            while let Some((base, state)) = {
339                item.borrow().sub_component().map(|c| {
340                    (
341                        c.root_element.clone(),
342                        builder.enter_component(&item, c, children_offset, &component_state),
343                    )
344                })
345            } {
346                item = base;
347                component_state = state;
348            }
349            builder.push_native_item(&item, children_offset, parent_index, &component_state)
350        }
351    }
352}
353
354/// Will call the `handle_property` callback for every property that needs to be initialized.
355/// This function makes sure to call them in order so that if constant binding need to access
356/// constant properties, these are already initialized
357pub fn handle_property_bindings_init(
358    component: &Rc<Component>,
359    mut handle_property: impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
360) {
361    fn handle_property_inner(
362        component: &Weak<Component>,
363        elem: &ElementRc,
364        prop_name: &SmolStr,
365        binding_expression: &BindingExpression,
366        handle_property: &mut impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
367        processed: &mut HashSet<NamedReference>,
368    ) {
369        if elem.borrow().is_component_placeholder {
370            return; // This element does not really exist!
371        }
372        let nr = NamedReference::new(elem, prop_name.clone());
373        if processed.contains(&nr) {
374            return;
375        }
376        processed.insert(nr);
377        if binding_expression.analysis.as_ref().is_some_and(|a| a.is_const) {
378            // We must first handle all dependent properties in case it is a constant property
379
380            binding_expression.expression.visit_recursive(&mut |e| {
381                if let Expression::PropertyReference(nr) = e {
382                    let elem = nr.element();
383                    if Weak::ptr_eq(&elem.borrow().enclosing_component, component)
384                        && let Some(be) = elem.borrow().bindings.get(nr.name())
385                    {
386                        handle_property_inner(
387                            component,
388                            &elem,
389                            nr.name(),
390                            &be.borrow(),
391                            handle_property,
392                            processed,
393                        );
394                    }
395                }
396            })
397        }
398        handle_property(elem, prop_name, binding_expression);
399    }
400
401    let mut processed = HashSet::new();
402    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
403        for (prop_name, binding_expression) in &elem.borrow().bindings {
404            handle_property_inner(
405                &Rc::downgrade(component),
406                elem,
407                prop_name,
408                &binding_expression.borrow(),
409                &mut handle_property,
410                &mut processed,
411            );
412        }
413    });
414}
415
416/// Call the given function for each constant property in the Component so one can set
417/// `set_constant` on it.
418pub fn for_each_const_properties(
419    component: &Rc<Component>,
420    mut f: impl FnMut(&ElementRc, &SmolStr),
421) {
422    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
423        if elem.borrow().repeated.is_some() {
424            return;
425        }
426        let mut e = elem.clone();
427        let mut all_prop = BTreeSet::new();
428        loop {
429            all_prop.extend(
430                e.borrow()
431                    .property_declarations
432                    .iter()
433                    .filter(|(_, x)| {
434                        x.property_type.is_property_type() &&
435                            !matches!( &x.property_type, crate::langtype::Type::Struct(s) if matches!(s.name, StructName::BuiltinPrivate(BuiltinPrivateStruct::StateInfo)))
436                    })
437                    .map(|(k, _)| k.clone()),
438            );
439            match &e.clone().borrow().base_type {
440                ElementType::Component(c) => {
441                    e = c.root_element.clone();
442                }
443                ElementType::Native(n) => {
444                    let mut n = n;
445                    loop {
446                        all_prop.extend(
447                            n.properties
448                                .iter()
449                                .filter(|(k, x)| {
450                                    x.ty.is_property_type()
451                                        && !k.starts_with("viewport-")
452                                        && k.as_str() != "commands"
453                                })
454                                .map(|(k, _)| k.clone()),
455                        );
456                        match n.parent.as_ref() {
457                            Some(p) => n = p,
458                            None => break,
459                        }
460                    }
461                    break;
462                }
463                ElementType::Builtin(_) => {
464                    unreachable!("builtin element should have been resolved")
465                }
466                ElementType::Global | ElementType::Interface | ElementType::Error => break,
467            }
468        }
469        for c in all_prop {
470            if NamedReference::new(elem, c.clone()).is_constant() {
471                f(elem, &c);
472            }
473        }
474    });
475}
476
477/// Convert a ascii kebab string to pascal case
478pub fn to_pascal_case(str: &str) -> String {
479    let mut result = Vec::with_capacity(str.len());
480    let mut next_upper = true;
481    for x in str.as_bytes() {
482        if *x == b'-' {
483            next_upper = true;
484        } else if next_upper {
485            result.push(x.to_ascii_uppercase());
486            next_upper = false;
487        } else {
488            result.push(*x);
489        }
490    }
491    String::from_utf8(result).unwrap()
492}
493
494/// Convert a ascii pascal case string to kebab case
495pub fn to_kebab_case(str: &str) -> String {
496    let mut result = Vec::with_capacity(str.len());
497    for x in str.as_bytes() {
498        if x.is_ascii_uppercase() {
499            if !result.is_empty() {
500                result.push(b'-');
501            }
502            result.push(x.to_ascii_lowercase());
503        } else {
504            result.push(*x);
505        }
506    }
507    String::from_utf8(result).unwrap()
508}
509
510#[test]
511fn case_conversions() {
512    assert_eq!(to_kebab_case("HelloWorld"), "hello-world");
513    assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
514}