sixtyfps_compilerlib/
generator.rs

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