/*! module for the C++ code generator */ /// This module contains some datastructure that helps represent a C++ code. /// It is then rendered into an actual C++ text using the Display trait mod cpp_ast { use std::cell::Cell; use std::fmt::{Display, Error, Formatter}; thread_local!(static INDETATION : Cell = Cell::new(0)); fn indent(f: &mut Formatter<'_>) -> Result<(), Error> { INDETATION.with(|i| { for _ in 0..(i.get()) { write!(f, " ")?; } Ok(()) }) } ///A full C++ file #[derive(Default, Debug)] pub struct File { pub includes: Vec, pub declarations: Vec, } impl Display for File { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { for i in &self.includes { writeln!(f, "#include {}", i)?; } for d in &self.declarations { write!(f, "\n{}", d)?; } Ok(()) } } /// Declarations (top level, or within a struct) #[derive(Debug, derive_more::Display)] pub enum Declaration { Struct(Struct), Function(Function), Var(Var), } #[derive(Default, Debug)] pub struct Struct { pub name: String, pub members: Vec, } impl Display for Struct { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; writeln!(f, "struct {} {{", self.name)?; INDETATION.with(|x| x.set(x.get() + 1)); for m in &self.members { // FIXME! identation write!(f, "{}", m)?; } INDETATION.with(|x| x.set(x.get() - 1)); indent(f)?; writeln!(f, "}};") } } /// Function or method #[derive(Default, Debug)] pub struct Function { pub name: String, /// "(...) -> ..." pub signature: String, /// The function does not have return type pub is_constructor: bool, pub is_static: bool, /// The list of statement instead the function. When None, this is just a function /// declaration without the definition pub statements: Option>, } impl Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; if self.is_static { write!(f, "static ")?; } if !self.is_constructor { write!(f, "auto ")?; } write!(f, "{} {}", self.name, self.signature)?; if let Some(st) = &self.statements { writeln!(f, "{{")?; for s in st { indent(f)?; writeln!(f, " {}", s)?; } indent(f)?; writeln!(f, "}}") } else { writeln!(f, ";") } } } /// A variable or a member declaration. #[derive(Default, Debug)] pub struct Var { pub ty: String, pub name: String, pub init: Option, } impl Display for Var { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; write!(f, "{} {}", self.ty, self.name)?; if let Some(i) = &self.init { write!(f, " = {}", i)?; } writeln!(f, ";") } } pub trait CppType { fn cpp_type(&self) -> Result<&str, crate::diagnostics::CompilerDiagnostic>; } } use crate::diagnostics::{CompilerDiagnostic, Diagnostics}; use crate::object_tree::{Component, Element, PropertyDeclaration}; use crate::typeregister::Type; use cpp_ast::*; use std::cell::RefCell; use std::rc::Rc; impl CppType for PropertyDeclaration { fn cpp_type(&self) -> Result<&str, CompilerDiagnostic> { match self.property_type { Type::Float32 => Ok("float"), Type::Int32 => Ok("int"), Type::String => Ok("sixtyfps::SharedString"), Type::Color => Ok("uint32_t"), Type::Bool => Ok("bool"), _ => Err(CompilerDiagnostic { message: "Cannot map property type to C++".into(), span: self.type_location.clone(), }), } } } fn handle_item(item: &Element, main_struct: &mut Struct, init: &mut Vec) { main_struct.members.push(Declaration::Var(Var { ty: format!("sixtyfps::{}", item.base_type.as_builtin().class_name), name: item.id.clone(), ..Default::default() })); let id = &item.id; init.extend(item.bindings.iter().map(|(s, i)| { if matches!(item.lookup_property(s.as_str()), Type::Signal) { let signal_accessor_prefix = if item.property_declarations.contains_key(s) { String::new() } else { format!("{id}.", id = id.clone()) }; format!( "{signal_accessor_prefix}{prop}.set_handler( [](const sixtyfps::EvaluationContext *context) {{ auto self = reinterpret_cast(context->component.instance); {code}; }});", signal_accessor_prefix = signal_accessor_prefix, prop = s, ty = main_struct.name, code = compile_expression(i) ) } else { let accessor_prefix = if item.property_declarations.contains_key(s) { String::new() } else { format!("{id}.", id = id.clone()) }; let init = compile_expression(i); if i.is_constant() { format!( "{accessor_prefix}{cpp_prop}.set({init});", accessor_prefix = accessor_prefix, cpp_prop = s, init = init ) } else { format!( "{accessor_prefix}{cpp_prop}.set_binding( [](const sixtyfps::EvaluationContext *context) {{ auto self = reinterpret_cast(context->component.instance); return {init}; }} );", accessor_prefix = accessor_prefix, cpp_prop = s, ty = main_struct.name, init = init ) } } })); for i in &item.children { handle_item(&i.borrow(), main_struct, init) } } /// Returns the text of the C++ code produced by the given root component pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option { let mut x = File::default(); x.includes.push("".into()); let mut main_struct = Struct { name: component.id.clone(), ..Default::default() }; main_struct.members.extend(component.root_element.borrow().property_declarations.iter().map( |(cpp_name, property_decl)| { let ty = if property_decl.property_type == Type::Signal { "sixtyfps::Signal".into() } else { let cpp_type = property_decl.cpp_type().unwrap_or_else(|err| { diag.push_compiler_error(err); "".into() }); format!("sixtyfps::Property<{}>", cpp_type) }; Declaration::Var(Var { ty, name: cpp_name.clone(), init: None }) }, )); let mut init = vec!["auto self = this;".into()]; handle_item(&component.root_element.borrow(), &mut main_struct, &mut init); main_struct.members.push(Declaration::Function(Function { name: component.id.clone(), signature: "()".to_owned(), is_constructor: true, statements: Some(init), ..Default::default() })); main_struct.members.push(Declaration::Function(Function { name: "tree_fn".into(), signature: "(sixtyfps::ComponentRef) -> const sixtyfps::ItemTreeNode* ".into(), is_static: true, ..Default::default() })); main_struct.members.push(Declaration::Var(Var { ty: "static const sixtyfps::ComponentVTable".to_owned(), name: "component_type".to_owned(), init: None, })); x.declarations.push(Declaration::Struct(main_struct)); let mut tree_array = String::new(); super::build_array_helper(component, |item, children_offset| { let item = item.borrow(); tree_array = format!( "{}{}sixtyfps::make_item_node(offsetof({}, {}), &sixtyfps::{}, {}, {})", tree_array, if tree_array.is_empty() { "" } else { ", " }, &component.id, item.id, item.base_type.as_builtin().vtable_symbol, item.children.len(), children_offset, ) }); x.declarations.push(Declaration::Function(Function { name: format!("{}::tree_fn", component.id), signature: "(sixtyfps::ComponentRef) -> const sixtyfps::ItemTreeNode* ".into(), statements: Some(vec![ "static const sixtyfps::ItemTreeNode children[] {".to_owned(), format!(" {} }};", tree_array), "return children;".to_owned(), ]), ..Default::default() })); x.declarations.push(Declaration::Var(Var { ty: "const sixtyfps::ComponentVTable".to_owned(), name: format!("{}::component_type", component.id), init: Some("{ nullptr, sixtyfps::dummy_destory, tree_fn }".to_owned()), })); if diag.has_error() { None } else { Some(x) } } fn access_member(element: &Rc>, name: &str) -> String { let e = element.borrow(); if e.property_declarations.contains_key(name) { name.into() } else { format!("{}.{}", e.id.as_str(), name) } } fn compile_expression(e: &crate::expression_tree::Expression) -> String { use crate::expression_tree::Expression::*; match e { StringLiteral(s) => format!(r#"sixtyfps::SharedString("{}")"#, s.escape_default()), NumberLiteral(n) => n.to_string(), PropertyReference { element, name, .. } => format!( r#"self->{}.get(context)"#, access_member(&element.upgrade().unwrap(), name.as_str()) ), SignalReference { element, name, .. } => { format!(r#"self->{}"#, access_member(&element.upgrade().unwrap(), name.as_str())) } Cast { from, to } => { let f = compile_expression(&*from); match (from.ty(), to) { (Type::Float32, Type::String) | (Type::Int32, Type::String) => { format!("sixtyfps::SharedString::from_number({})", f) } _ => f, } } CodeBlock(sub) => { let mut x = sub.iter().map(|e| compile_expression(e)).collect::>(); x.last_mut().map(|s| *s = format!("return {};", s)); format!("[&]{{ {} }}()", x.join(";")) } FunctionCall { function } => { if matches!(function.ty(), Type::Signal) { format!("{}.emit(context)", compile_expression(&*function)) } else { format!("\n#error the function `{:?}` is not a signal\n", function) } } SelfAssignement { lhs, rhs, op } => match &**lhs { PropertyReference { element, name, .. } => format!( r#"self->{lhs}.set(self->{lhs}.get(context) {op} {rhs})"#, lhs = access_member(&element.upgrade().unwrap(), name.as_str()), rhs = compile_expression(&*rhs), op = op ), _ => panic!("typechecking should make sure this was a PropertyReference"), }, ResourceReference { absolute_source_path } => { format!(r#"sixtyfps::Resource(sixtyfps::SharedString("{}"))"#, absolute_source_path) } Uncompiled(_) => panic!(), Invalid => format!("\n#error invalid expression\n"), } }