slint/sixtyfps_compiler/generator/cpp.rs
Simon Hausmann 5bae6e01a5 Prepare for the ability to embed image data
The Image's source property used to be a string. Now it is a Resource
enum, which can either be None or an absolute file path to the image on
disk. This also replaces the internal Image type.

The compiler internally resolves the img bang expression to a resource
reference, which shall remain just an absolute path. For now the target
generator passes that through, but in the future the target generator
may choose a target specific way of embedding the data and thus
generating a different Resource type in the final code (through
compile_expression in the cpp and rust generator).

The C++ binding is a bit messy as cbindgen doesn't really support
exporting enums that can be constructed on the C++ side. So instead we
use cbindgen to merely export the type internally and only use the tag
from it then. The public API is then a custom Resource type that is
meant to be binary compatible.
2020-06-09 22:54:29 +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)
}
}
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"),
}
}