Support for code blocks and signal call

This commit is contained in:
Olivier Goffart 2020-05-28 12:25:26 +02:00
parent f2df9293a9
commit 95b671c97c
11 changed files with 240 additions and 84 deletions

View file

@ -192,13 +192,18 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
}; };
let rust_property = quote!(#rust_property_accessor_prefix#rust_property_ident); let rust_property = quote!(#rust_property_accessor_prefix#rust_property_ident);
let v = compile_expression(v, &component_id); let v = compile_expression(v);
if v.is_none() { if v.is_none() {
// FIXME: this is because signals are not yet implemented // FIXME: this is because signals are not yet implemented
continue; continue;
} }
init.push(quote!(self_.#rust_property#v;)); init.push(quote!(
self_.#rust_property.set_binding(|context| {
let _self = context.component.downcast::<#component_id>().unwrap();
#v
});
));
} }
item_names.push(field_name); item_names.push(field_name);
item_types.push(quote::format_ident!("{}", item.base_type.as_builtin().class_name)); item_types.push(quote::format_ident!("{}", item.base_type.as_builtin().class_name));
@ -251,31 +256,43 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream {
result.into() result.into()
} }
fn compile_expression( fn compile_expression(e: &Expression) -> Option<proc_macro2::TokenStream> {
e: &Expression,
self_id: &proc_macro2::Ident,
) -> Option<proc_macro2::TokenStream> {
Some(match e { Some(match e {
Expression::StringLiteral(s) => quote!(.set(sixtyfps::re_exports::SharedString::from(#s))), Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)),
Expression::NumberLiteral(n) => quote!(.set(#n as _)), Expression::NumberLiteral(n) => quote!(#n as _),
Expression::Cast { from, to } => { Expression::Cast { from, to } => {
let f = compile_expression(&*from, self_id)?; let f = compile_expression(&*from)?;
match (from.ty(), to) { match (from.ty(), to) {
(Type::Float32, Type::String) | (Type::Int32, Type::String) => { (Type::Float32, Type::String) | (Type::Int32, Type::String) => {
quote!(.set(sixtyfps::re_exports::SharedString::from(format("{}", #f).to_str()))) quote!(sixtyfps::re_exports::SharedString::from(format("{}", #f).to_str()))
} }
_ => f, _ => f,
} }
} }
Expression::PropertyReference { component: _, element: _, name } => { Expression::PropertyReference { component: _, element, name } => {
let name_ident = quote::format_ident!("{}", name); let name_ident = quote::format_ident!("{}", name);
quote!(.set_binding(|context| { let e = element.upgrade().unwrap();
let _self: &#self_id = context.component.downcast().unwrap(); if !e.borrow().property_declarations.contains_key(name) {
_self.#name_ident.get(context) let elem_ident = quote::format_ident!("{}", e.borrow().id);
})) quote!(_self.#elem_ident.#name_ident.get(context))
} else {
quote!(_self.#name_ident.get(context))
}
}
Expression::CodeBlock(sub) => {
if sub.iter().map(|e| compile_expression(e)).any(|e| e.is_none()) {
// fixme: Remove!
return None;
}
let map = sub.iter().map(|e| compile_expression(e));
quote!({ #(#map);* })
} }
// FIXME: signals! // FIXME: signals!
Expression::SignalReference { .. } => return None, Expression::SignalReference { .. } => return None,
_ => quote!(compile_error! {"unsupported expression"}), Expression::FunctionCall { .. } => return None,
_ => {
let error = format!("unsupported expression {:?}", e);
quote!(compile_error! {#error})
}
}) })
} }

View file

@ -13,7 +13,7 @@ component TwoRectangle := Rectangle {
my_area := TouchArea { my_area := TouchArea {
width: 25; width: 25;
height: 25; height: 25;
clicked => { clicked } clicked => { root.clicked() }
} }
} }
} }
@ -27,7 +27,7 @@ component ButtonRectangle := Rectangle {
TouchArea { TouchArea {
width: 100; width: 100;
height: 75; height: 75;
clicked => { clicked } clicked => { root.clicked() }
} }
Text { Text {
x: 50; x: 50;
@ -49,7 +49,7 @@ Hello := Rectangle {
width: 100; width: 100;
height: 100; height: 100;
color: blue; color: blue;
clicked => { foobar } clicked => { foobar() }
} }
Rectangle { Rectangle {
x: 100; x: 100;
@ -75,7 +75,7 @@ Hello := Rectangle {
color: 4289374890; color: 4289374890;
x: 50; x: 50;
y: 225; y: 225;
clicked => { plus_clicked } clicked => { plus_clicked() }
button_text: "+"; button_text: "+";
} }
property<int32> counter; property<int32> counter;
@ -84,7 +84,7 @@ Hello := Rectangle {
color: 4289374890; color: 4289374890;
x: 50; x: 50;
y: 350; y: 350;
clicked => { minus_clicked } clicked => { minus_clicked() }
button_text: "-"; button_text: "-";
} }

View file

@ -15,7 +15,7 @@ component TwoRectangle := Rectangle {
my_area := TouchArea { my_area := TouchArea {
width: 25; width: 25;
height: 25; height: 25;
clicked => { clicked } clicked => { root.clicked() }
} }
} }
@ -30,7 +30,7 @@ component ButtonRectangle := Rectangle {
TouchArea { TouchArea {
width: 100; width: 100;
height: 75; height: 75;
clicked => { clicked } clicked => { root.clicked() }
} }
Text { Text {
x: 50; x: 50;
@ -52,7 +52,7 @@ Hello := Rectangle {
width: 100; width: 100;
height: 100; height: 100;
color: blue; color: blue;
clicked => { foobar } clicked => { foobar() }
} }
Rectangle { Rectangle {
x: 100; x: 100;
@ -78,7 +78,7 @@ Hello := Rectangle {
color: 4289374890; color: 4289374890;
x: 50; x: 50;
y: 225; y: 225;
clicked => { plus_clicked } clicked => { plus_clicked() }
button_text: "+"; button_text: "+";
} }
counter := Text { x: 100; y: 300; text: "0"; color: black; } counter := Text { x: 100; y: 300; text: "0"; color: black; }
@ -86,7 +86,7 @@ Hello := Rectangle {
color: 4289374890; color: 4289374890;
x: 50; x: 50;
y: 350; y: 350;
clicked => { minus_clicked } clicked => { minus_clicked() }
button_text: "-"; button_text: "-";
} }

View file

@ -27,6 +27,12 @@ pub enum Expression {
/// Cast an expression to the given type /// Cast an expression to the given type
Cast { from: Box<Expression>, to: Type }, Cast { from: Box<Expression>, to: Type },
/// a code block with different expression
CodeBlock(Vec<Expression>),
/// A function call
FunctionCall { function: Box<Expression> },
} }
impl Expression { impl Expression {
@ -42,6 +48,8 @@ impl Expression {
element.upgrade().unwrap().borrow().lookup_property(name) element.upgrade().unwrap().borrow().lookup_property(name)
} }
Expression::Cast { to, .. } => to.clone(), Expression::Cast { to, .. } => to.clone(),
Expression::CodeBlock(sub) => sub.last().map_or(Type::Invalid, |e| e.ty()),
Expression::FunctionCall { function } => function.ty(),
} }
} }
@ -55,6 +63,12 @@ impl Expression {
Expression::SignalReference { .. } => {} Expression::SignalReference { .. } => {}
Expression::PropertyReference { .. } => {} Expression::PropertyReference { .. } => {}
Expression::Cast { from, .. } => visitor(&**from), Expression::Cast { from, .. } => visitor(&**from),
Expression::CodeBlock(sub) => {
for e in sub {
visitor(e)
}
}
Expression::FunctionCall { function } => visitor(&**function),
} }
} }
@ -67,6 +81,12 @@ impl Expression {
Expression::SignalReference { .. } => {} Expression::SignalReference { .. } => {}
Expression::PropertyReference { .. } => {} Expression::PropertyReference { .. } => {}
Expression::Cast { from, .. } => visitor(&mut **from), Expression::Cast { from, .. } => visitor(&mut **from),
Expression::CodeBlock(sub) => {
for e in sub {
visitor(e)
}
}
Expression::FunctionCall { function } => visitor(&mut **function),
} }
} }
} }

View file

@ -162,9 +162,7 @@ fn handle_item(
let id = &item.id; let id = &item.id;
init.extend(item.bindings.iter().map(|(s, i)| { init.extend(item.bindings.iter().map(|(s, i)| {
use crate::expression_tree::Expression; if matches!(item.lookup_property(s.as_str()), Type::Signal) {
match i {
Expression::SignalReference { component:_, element:_, name } => {
let signal_accessor_prefix = if item.signals_declaration.contains(s) { let signal_accessor_prefix = if item.signals_declaration.contains(s) {
String::new() String::new()
} else { } else {
@ -172,11 +170,10 @@ fn handle_item(
}; };
format!( format!(
"{signal_accessor_prefix}{prop}.set_handler([](const void *root) {{ reinterpret_cast<const {ty}*>(root)->{fwd}.emit(root); }});", "{signal_accessor_prefix}{prop}.set_handler([](const void *self_) {{ auto self = reinterpret_cast<const {ty}*>(self_); {code}; }});",
signal_accessor_prefix = signal_accessor_prefix, prop = s, fwd = name.clone(), ty = main_struct.name 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) { let accessor_prefix = if item.property_declarations.contains_key(s) {
String::new() String::new()
} else { } else {
@ -191,7 +188,6 @@ fn handle_item(
init = init init = init
) )
} }
}
})); }));
for i in &item.children { for i in &item.children {
@ -225,7 +221,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option<impl st
main_struct.members.extend(declared_property_vars); main_struct.members.extend(declared_property_vars);
let mut init = Vec::new(); let mut init = vec!["auto self = this;".into()];
handle_item( handle_item(
&component.root_element.borrow(), &component.root_element.borrow(),
&declared_property_members, &declared_property_members,
@ -303,7 +299,23 @@ fn compile_expression(e: &crate::expression_tree::Expression) -> String {
match e { match e {
StringLiteral(s) => format!(r#"sixtyfps::SharedString("{}")"#, s.escape_default()), StringLiteral(s) => format!(r#"sixtyfps::SharedString("{}")"#, s.escape_default()),
NumberLiteral(n) => n.to_string(), NumberLiteral(n) => n.to_string(),
PropertyReference { name, .. } => format!(r#"{}.get(nullptr)"#, name), PropertyReference { element, name, .. } => {
let e = element.upgrade().unwrap();
let e = e.borrow();
let (elem, dot) = if e.property_declarations.contains_key(name) {
("", "")
} else {
(e.id.as_str(), ".")
};
format!(r#"self->{}{}{}.get(nullptr)"#, elem, dot, name)
}
SignalReference { element, name, .. } => {
let e = element.upgrade().unwrap();
let e = e.borrow();
let (elem, dot) =
if e.signals_declaration.contains(name) { ("", "") } else { (e.id.as_str(), ".") };
format!(r#"self->{}{}{}"#, elem, dot, name)
}
Cast { from, to } => { Cast { from, to } => {
let f = compile_expression(&*from); let f = compile_expression(&*from);
match (from.ty(), to) { match (from.ty(), to) {
@ -313,6 +325,20 @@ fn compile_expression(e: &crate::expression_tree::Expression) -> String {
_ => f, _ => f,
} }
} }
_ => format!("\n#error: unsupported expression {:?}\n", e), 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(self_)", compile_expression(&*function))
} else {
format!("\n#error the function `{:?}` is not a signal\n", function)
}
}
Uncompiled(_) => panic!(),
Invalid => format!("\n#error invalid expression\n"),
} }
} }

View file

@ -259,6 +259,9 @@ impl Element {
} }
pub fn lookup_property(&self, name: &str) -> Type { pub fn lookup_property(&self, name: &str) -> Type {
if self.signals_declaration.iter().any(|x| x == name) {
return Type::Signal;
}
self.property_declarations self.property_declarations
.get(name) .get(name)
.cloned() .cloned()

View file

@ -47,13 +47,17 @@ macro_rules! declare_token_kind {
SignalDeclaration, SignalDeclaration,
SignalConnection, SignalConnection,
PropertyDeclaration, PropertyDeclaration,
/// wraps Identifiers, like Rectangle or SomeModule.SomeType
QualifiedName,
Binding, Binding,
BindingExpression, // the right-hand-side of a binding /// the right-hand-side of a binding
BindingExpression,
CodeBlock, CodeBlock,
Expression, Expression,
QualifiedName, // wraps Identifiers, like Rectangle or SomeModule.SomeType
/// foo!bar /// foo!bar
BangExpression, BangExpression,
/// expression()
FunctionCallExpression,
} }
fn lexer() -> m_lexer::Lexer { fn lexer() -> m_lexer::Lexer {
@ -124,22 +128,36 @@ mod parser_trait {
//! module allowing to keep implementation details of the node private //! module allowing to keep implementation details of the node private
use super::*; use super::*;
// Todo: rename DefaultParser
pub trait Parser: Sized { pub trait Parser: Sized {
type Checkpoint: Clone;
/// Enter a new node. The node is going to be finished when /// Enter a new node. The node is going to be finished when
/// The return value of this function is drop'ed /// The return value of this function is drop'ed
/// ///
/// (do not re-implement this function, re-implement /// (do not re-implement this function, re-implement
/// start_node_impl and finish_node_impl) /// start_node_impl and finish_node_impl)
#[must_use = "The node will be finished when it is dropped"]
fn start_node(&mut self, kind: SyntaxKind) -> Node<Self> { fn start_node(&mut self, kind: SyntaxKind) -> Node<Self> {
self.start_node_impl(kind, NodeToken(())); self.start_node_impl(kind, None, NodeToken(()));
Node(self)
}
#[must_use = "use start_node_at to use this checkpoint"]
fn checkpoint(&mut self) -> Self::Checkpoint;
#[must_use = "The node will be finished when it is dropped"]
fn start_node_at(&mut self, checkpoint: Self::Checkpoint, kind: SyntaxKind) -> Node<Self> {
self.start_node_impl(kind, Some(checkpoint), NodeToken(()));
Node(self) Node(self)
} }
/// Can only be called by Node::drop /// Can only be called by Node::drop
fn finish_node_impl(&mut self, token: NodeToken); fn finish_node_impl(&mut self, token: NodeToken);
/// Can only be called by Self::start_node /// Can only be called by Self::start_node
fn start_node_impl(&mut self, kind: SyntaxKind, token: NodeToken); fn start_node_impl(
&mut self,
kind: SyntaxKind,
checkpoint: Option<Self::Checkpoint>,
token: NodeToken,
);
fn peek(&mut self) -> Token; fn peek(&mut self) -> Token;
/// Peek the n'th token, not including whitespaces and comments /// Peek the n'th token, not including whitespaces and comments
fn nth(&mut self, n: usize) -> SyntaxKind; fn nth(&mut self, n: usize) -> SyntaxKind;
@ -243,8 +261,16 @@ impl DefaultParser {
} }
impl Parser for DefaultParser { impl Parser for DefaultParser {
fn start_node_impl(&mut self, kind: SyntaxKind, _: NodeToken) { fn start_node_impl(
self.builder.start_node(kind.into()); &mut self,
kind: SyntaxKind,
checkpoint: Option<Self::Checkpoint>,
_: NodeToken,
) {
match checkpoint {
None => self.builder.start_node(kind.into()),
Some(cp) => self.builder.start_node_at(cp, kind.into()),
}
} }
fn finish_node_impl(&mut self, _: NodeToken) { fn finish_node_impl(&mut self, _: NodeToken) {
@ -290,6 +316,11 @@ impl Parser for DefaultParser {
} }
self.diags.push_error(e.into(), span); self.diags.push_error(e.into(), span);
} }
type Checkpoint = rowan::Checkpoint;
fn checkpoint(&mut self) -> Self::Checkpoint {
self.builder.checkpoint()
}
} }
#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]

View file

@ -10,9 +10,11 @@ use super::prelude::*;
/// (something) /// (something)
/// img!"something" /// img!"something"
/// some_id.some_property /// some_id.some_property
/// function_call()
/// ``` /// ```
pub fn parse_expression(p: &mut impl Parser) { pub fn parse_expression(p: &mut impl Parser) {
let mut p = p.start_node(SyntaxKind::Expression); let mut p = p.start_node(SyntaxKind::Expression);
let checkpoint = p.checkpoint();
match p.nth(0) { match p.nth(0) {
SyntaxKind::Identifier => { SyntaxKind::Identifier => {
if p.nth(1) == SyntaxKind::Bang { if p.nth(1) == SyntaxKind::Bang {
@ -28,7 +30,23 @@ pub fn parse_expression(p: &mut impl Parser) {
parse_expression(&mut *p); parse_expression(&mut *p);
p.expect(SyntaxKind::RParent); p.expect(SyntaxKind::RParent);
} }
_ => p.error("invalid expression"), _ => {
p.error("invalid expression");
return;
}
}
match p.nth(0) {
SyntaxKind::LParent => {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint, SyntaxKind::FunctionCallExpression);
p.consume();
p.expect(SyntaxKind::RParent);
}
_ => {}
} }
} }

View file

@ -77,15 +77,26 @@ impl Expression {
debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression); debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression);
let e = node let e = node
.child_node(SyntaxKind::Expression) .child_node(SyntaxKind::Expression)
.map(|n| Self::from_expression_node(n, ctx))
.or_else(|| { .or_else(|| {
node.child_node(SyntaxKind::CodeBlock) node.child_node(SyntaxKind::CodeBlock).map(|c| Self::from_codeblock_node(c, ctx))
.and_then(|c| c.child_node(SyntaxKind::Expression))
}) })
.map_or(Self::Invalid, |n| Self::from_expression_node(n, ctx)); .unwrap_or(Self::Invalid);
maybe_convert_to(e, ctx, &node) maybe_convert_to(e, ctx, &node)
} }
fn from_codeblock_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Expression {
debug_assert_eq!(node.kind(), SyntaxKind::CodeBlock);
Expression::CodeBlock(
node.children()
.filter(|n| n.kind() == SyntaxKind::Expression)
.map(|n| Self::from_expression_node(n, ctx))
.collect(),
)
}
fn from_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self { fn from_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self {
debug_assert_eq!(node.kind(), SyntaxKind::Expression);
node.child_node(SyntaxKind::Expression) node.child_node(SyntaxKind::Expression)
.map(|n| Self::from_expression_node(n, ctx)) .map(|n| Self::from_expression_node(n, ctx))
.or_else(|| { .or_else(|| {
@ -112,6 +123,17 @@ impl Expression {
}) })
}) })
}) })
.or_else(|| {
node.child_node(SyntaxKind::FunctionCallExpression).map(|n| {
Expression::FunctionCall {
function: Box::new(
n.child_node(SyntaxKind::Expression)
.map(|n| Self::from_expression_node(n, ctx))
.unwrap_or(Expression::Invalid),
),
}
})
})
.unwrap_or(Self::Invalid) .unwrap_or(Self::Invalid)
} }
@ -186,6 +208,18 @@ impl Expression {
element: Rc::downgrade(&ctx.component.root_element), element: Rc::downgrade(&ctx.component.root_element),
name: s.to_string(), name: s.to_string(),
}; };
} else if matches!(property, Type::Signal) {
if let Some(x) = it.next() {
ctx.diag.push_error(
"Cannot access fields of signal".into(),
x.into_token().unwrap().span(),
)
}
return Self::SignalReference {
component: Rc::downgrade(&ctx.component),
element: Rc::downgrade(&ctx.component.root_element),
name: s.to_string(),
};
} else if property.is_object_type() { } else if property.is_object_type() {
todo!("Continue lookling up"); todo!("Continue lookling up");
} }
@ -211,6 +245,18 @@ impl Expression {
element: Rc::downgrade(&elem), element: Rc::downgrade(&elem),
name: prop_name.text().to_string(), name: prop_name.text().to_string(),
}; };
} else if matches!(p, Type::Signal) {
if let Some(x) = it.next() {
ctx.diag.push_error(
"Cannot access fields of signal".into(),
x.into_token().unwrap().span(),
)
}
return Self::SignalReference {
component: Rc::downgrade(&ctx.component),
element: Rc::downgrade(&elem),
name: prop_name.to_string(),
};
} else { } else {
ctx.diag.push_error( ctx.diag.push_error(
format!("Cannot access property '{}'", prop_name), format!("Cannot access property '{}'", prop_name),
@ -225,20 +271,6 @@ impl Expression {
return Expression::Invalid; return Expression::Invalid;
} }
if matches!(ctx.property_type, Type::Signal) {
if let Some(x) = it.next() {
ctx.diag.push_error(
"Cannot access fields of signal".into(),
x.into_token().unwrap().span(),
)
}
return Self::SignalReference {
component: Rc::downgrade(&ctx.component),
element: Rc::downgrade(&ctx.component.root_element),
name: s.to_string(),
};
}
if matches!(ctx.property_type, Type::Color) { if matches!(ctx.property_type, Type::Color) {
let value: Option<u32> = match s { let value: Option<u32> = match s {
"blue" => Some(0xff0000ff), "blue" => Some(0xff0000ff),

View file

@ -3,7 +3,7 @@ SubElements := Rectangle {
signal foobar; signal foobar;
TouchArea { TouchArea {
clicked => { foobar } clicked => { foobar() }
} }
TouchArea { TouchArea {
@ -14,8 +14,8 @@ SubElements := Rectangle {
} }
TouchArea { TouchArea {
clicked => { foobar } clicked => { foobar() }
clicked => { foobar } clicked => { foobar() }
// ^error{Duplicated signal} // ^error{Duplicated signal}
} }

View file

@ -4,6 +4,7 @@ use sixtyfps_compiler::typeregister::Type;
#[derive(Debug)] #[derive(Debug)]
pub enum Value { pub enum Value {
Void,
Number(f64), Number(f64),
String(SharedString), String(SharedString),
} }
@ -42,5 +43,13 @@ pub fn eval_expression(
(v, _) => v, (v, _) => v,
} }
} }
Expression::CodeBlock(sub) => {
let mut v = Value::Void;
for e in sub {
v = eval_expression(e, ctx, component_ref);
}
v
}
Expression::FunctionCall { .. } => todo!("function call"),
} }
} }