slint/sixtyfps_compiler/generator/cpp.rs
Simon Hausmann ea52c7c9c9 Fix typo
2020-06-10 12:03:46 +02:00

363 lines
12 KiB
Rust

/*! 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<u32> = 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<String>,
pub declarations: Vec<Declaration>,
}
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<Declaration>,
}
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<Vec<String>>,
}
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<String>,
}
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<String>) {
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<const {ty}*>(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<const {ty}*>(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<impl std::fmt::Display> {
let mut x = File::default();
x.includes.push("<sixtyfps.h>".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<RefCell<Element>>, 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::<Vec<_>>();
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)
}
}
SelfAssignment { 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"),
}
}