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