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