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