1use 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 )); }
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
105pub trait ItemTreeBuilder {
108 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 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 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
144pub 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 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 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
345pub 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; }
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 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
407pub 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
468pub 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
485pub 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}