From 95b671c97c89bd86b00aa0b92b8dd074a4bd0ae2 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 28 May 2020 12:25:26 +0200 Subject: [PATCH] Support for code blocks and signal call --- api/sixtyfps-rs/sixtyfps-rs-macro/lib.rs | 49 +++++++++----- examples/cpptest/hello.60 | 10 +-- examples/rusttest/src/main.rs | 10 +-- sixtyfps_compiler/expression_tree.rs | 20 ++++++ sixtyfps_compiler/generator/cpp.rs | 86 +++++++++++++++--------- sixtyfps_compiler/object_tree.rs | 3 + sixtyfps_compiler/parser.rs | 45 +++++++++++-- sixtyfps_compiler/parser/expressions.rs | 20 +++++- sixtyfps_compiler/passes/resolving.rs | 66 +++++++++++++----- sixtyfps_compiler/tests/basic/signal.60 | 6 +- tools/viewer/eval.rs | 9 +++ 11 files changed, 240 insertions(+), 84 deletions(-) diff --git a/api/sixtyfps-rs/sixtyfps-rs-macro/lib.rs b/api/sixtyfps-rs/sixtyfps-rs-macro/lib.rs index aebf59c43..4d4456cdd 100644 --- a/api/sixtyfps-rs/sixtyfps-rs-macro/lib.rs +++ b/api/sixtyfps-rs/sixtyfps-rs-macro/lib.rs @@ -192,13 +192,18 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream { }; 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() { // FIXME: this is because signals are not yet implemented 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_types.push(quote::format_ident!("{}", item.base_type.as_builtin().class_name)); @@ -251,31 +256,43 @@ pub fn sixtyfps(stream: TokenStream) -> TokenStream { result.into() } -fn compile_expression( - e: &Expression, - self_id: &proc_macro2::Ident, -) -> Option { +fn compile_expression(e: &Expression) -> Option { Some(match e { - Expression::StringLiteral(s) => quote!(.set(sixtyfps::re_exports::SharedString::from(#s))), - Expression::NumberLiteral(n) => quote!(.set(#n as _)), + Expression::StringLiteral(s) => quote!(sixtyfps::re_exports::SharedString::from(#s)), + Expression::NumberLiteral(n) => quote!(#n as _), Expression::Cast { from, to } => { - let f = compile_expression(&*from, self_id)?; + let f = compile_expression(&*from)?; match (from.ty(), to) { (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, } } - Expression::PropertyReference { component: _, element: _, name } => { + Expression::PropertyReference { component: _, element, name } => { let name_ident = quote::format_ident!("{}", name); - quote!(.set_binding(|context| { - let _self: &#self_id = context.component.downcast().unwrap(); - _self.#name_ident.get(context) - })) + let e = element.upgrade().unwrap(); + if !e.borrow().property_declarations.contains_key(name) { + 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! Expression::SignalReference { .. } => return None, - _ => quote!(compile_error! {"unsupported expression"}), + Expression::FunctionCall { .. } => return None, + _ => { + let error = format!("unsupported expression {:?}", e); + quote!(compile_error! {#error}) + } }) } diff --git a/examples/cpptest/hello.60 b/examples/cpptest/hello.60 index c9456e121..667cdf052 100644 --- a/examples/cpptest/hello.60 +++ b/examples/cpptest/hello.60 @@ -13,7 +13,7 @@ component TwoRectangle := Rectangle { my_area := TouchArea { width: 25; height: 25; - clicked => { clicked } + clicked => { root.clicked() } } } } @@ -27,7 +27,7 @@ component ButtonRectangle := Rectangle { TouchArea { width: 100; height: 75; - clicked => { clicked } + clicked => { root.clicked() } } Text { x: 50; @@ -49,7 +49,7 @@ Hello := Rectangle { width: 100; height: 100; color: blue; - clicked => { foobar } + clicked => { foobar() } } Rectangle { x: 100; @@ -75,7 +75,7 @@ Hello := Rectangle { color: 4289374890; x: 50; y: 225; - clicked => { plus_clicked } + clicked => { plus_clicked() } button_text: "+"; } property counter; @@ -84,7 +84,7 @@ Hello := Rectangle { color: 4289374890; x: 50; y: 350; - clicked => { minus_clicked } + clicked => { minus_clicked() } button_text: "-"; } diff --git a/examples/rusttest/src/main.rs b/examples/rusttest/src/main.rs index ec139a2dc..c58b519ec 100644 --- a/examples/rusttest/src/main.rs +++ b/examples/rusttest/src/main.rs @@ -15,7 +15,7 @@ component TwoRectangle := Rectangle { my_area := TouchArea { width: 25; height: 25; - clicked => { clicked } + clicked => { root.clicked() } } } @@ -30,7 +30,7 @@ component ButtonRectangle := Rectangle { TouchArea { width: 100; height: 75; - clicked => { clicked } + clicked => { root.clicked() } } Text { x: 50; @@ -52,7 +52,7 @@ Hello := Rectangle { width: 100; height: 100; color: blue; - clicked => { foobar } + clicked => { foobar() } } Rectangle { x: 100; @@ -78,7 +78,7 @@ Hello := Rectangle { color: 4289374890; x: 50; y: 225; - clicked => { plus_clicked } + clicked => { plus_clicked() } button_text: "+"; } counter := Text { x: 100; y: 300; text: "0"; color: black; } @@ -86,7 +86,7 @@ Hello := Rectangle { color: 4289374890; x: 50; y: 350; - clicked => { minus_clicked } + clicked => { minus_clicked() } button_text: "-"; } diff --git a/sixtyfps_compiler/expression_tree.rs b/sixtyfps_compiler/expression_tree.rs index c6189aee8..15c850ff8 100644 --- a/sixtyfps_compiler/expression_tree.rs +++ b/sixtyfps_compiler/expression_tree.rs @@ -27,6 +27,12 @@ pub enum Expression { /// Cast an expression to the given type Cast { from: Box, to: Type }, + + /// a code block with different expression + CodeBlock(Vec), + + /// A function call + FunctionCall { function: Box }, } impl Expression { @@ -42,6 +48,8 @@ impl Expression { element.upgrade().unwrap().borrow().lookup_property(name) } 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::PropertyReference { .. } => {} 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::PropertyReference { .. } => {} Expression::Cast { from, .. } => visitor(&mut **from), + Expression::CodeBlock(sub) => { + for e in sub { + visitor(e) + } + } + Expression::FunctionCall { function } => visitor(&mut **function), } } } diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index 0f1663e40..b8c28a1da 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -162,35 +162,31 @@ fn handle_item( let id = &item.id; init.extend(item.bindings.iter().map(|(s, i)| { - use crate::expression_tree::Expression; - match i { - Expression::SignalReference { component:_, element:_, name } => { - let signal_accessor_prefix = if item.signals_declaration.contains(s) { - String::new() - } else { - format!("{id}.", id = id.clone()) - }; + if matches!(item.lookup_property(s.as_str()), Type::Signal) { + let signal_accessor_prefix = if item.signals_declaration.contains(s) { + String::new() + } else { + format!("{id}.", id = id.clone()) + }; - format!( - "{signal_accessor_prefix}{prop}.set_handler([](const void *root) {{ reinterpret_cast(root)->{fwd}.emit(root); }});", - signal_accessor_prefix = signal_accessor_prefix, prop = s, fwd = name.clone(), ty = main_struct.name - ) - } - _ => { - let 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 void *self_) {{ auto self = reinterpret_cast(self_); {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); - format!( - "{accessor_prefix}{cpp_prop}.set({init});", - accessor_prefix = accessor_prefix, - cpp_prop = s, - init = init - ) - } + let init = compile_expression(i); + format!( + "{accessor_prefix}{cpp_prop}.set({init});", + accessor_prefix = accessor_prefix, + cpp_prop = s, + init = init + ) } })); @@ -225,7 +221,7 @@ pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option String { match e { StringLiteral(s) => format!(r#"sixtyfps::SharedString("{}")"#, s.escape_default()), 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 } => { let f = compile_expression(&*from); match (from.ty(), to) { @@ -313,6 +325,20 @@ fn compile_expression(e: &crate::expression_tree::Expression) -> String { _ => f, } } - _ => format!("\n#error: unsupported expression {:?}\n", e), + 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(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"), } } diff --git a/sixtyfps_compiler/object_tree.rs b/sixtyfps_compiler/object_tree.rs index 184b1aa47..fa5d5ee7c 100644 --- a/sixtyfps_compiler/object_tree.rs +++ b/sixtyfps_compiler/object_tree.rs @@ -259,6 +259,9 @@ impl Element { } pub fn lookup_property(&self, name: &str) -> Type { + if self.signals_declaration.iter().any(|x| x == name) { + return Type::Signal; + } self.property_declarations .get(name) .cloned() diff --git a/sixtyfps_compiler/parser.rs b/sixtyfps_compiler/parser.rs index cce7afb88..07f82ca3e 100644 --- a/sixtyfps_compiler/parser.rs +++ b/sixtyfps_compiler/parser.rs @@ -47,13 +47,17 @@ macro_rules! declare_token_kind { SignalDeclaration, SignalConnection, PropertyDeclaration, + /// wraps Identifiers, like Rectangle or SomeModule.SomeType + QualifiedName, Binding, - BindingExpression, // the right-hand-side of a binding + /// the right-hand-side of a binding + BindingExpression, CodeBlock, Expression, - QualifiedName, // wraps Identifiers, like Rectangle or SomeModule.SomeType /// foo!bar BangExpression, + /// expression() + FunctionCallExpression, } fn lexer() -> m_lexer::Lexer { @@ -124,22 +128,36 @@ mod parser_trait { //! module allowing to keep implementation details of the node private use super::*; - // Todo: rename DefaultParser pub trait Parser: Sized { + type Checkpoint: Clone; + /// Enter a new node. The node is going to be finished when /// The return value of this function is drop'ed /// /// (do not re-implement this function, re-implement /// 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.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.start_node_impl(kind, Some(checkpoint), NodeToken(())); Node(self) } /// Can only be called by Node::drop fn finish_node_impl(&mut self, token: NodeToken); /// 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, + token: NodeToken, + ); fn peek(&mut self) -> Token; /// Peek the n'th token, not including whitespaces and comments fn nth(&mut self, n: usize) -> SyntaxKind; @@ -243,8 +261,16 @@ impl DefaultParser { } impl Parser for DefaultParser { - fn start_node_impl(&mut self, kind: SyntaxKind, _: NodeToken) { - self.builder.start_node(kind.into()); + fn start_node_impl( + &mut self, + kind: SyntaxKind, + checkpoint: Option, + _: 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) { @@ -290,6 +316,11 @@ impl Parser for DefaultParser { } 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)] diff --git a/sixtyfps_compiler/parser/expressions.rs b/sixtyfps_compiler/parser/expressions.rs index 7f601cd54..786ec048e 100644 --- a/sixtyfps_compiler/parser/expressions.rs +++ b/sixtyfps_compiler/parser/expressions.rs @@ -10,9 +10,11 @@ use super::prelude::*; /// (something) /// img!"something" /// some_id.some_property +/// function_call() /// ``` pub fn parse_expression(p: &mut impl Parser) { let mut p = p.start_node(SyntaxKind::Expression); + let checkpoint = p.checkpoint(); match p.nth(0) { SyntaxKind::Identifier => { if p.nth(1) == SyntaxKind::Bang { @@ -28,7 +30,23 @@ pub fn parse_expression(p: &mut impl Parser) { parse_expression(&mut *p); 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); + } + _ => {} } } diff --git a/sixtyfps_compiler/passes/resolving.rs b/sixtyfps_compiler/passes/resolving.rs index 773a8085b..54c106b60 100644 --- a/sixtyfps_compiler/passes/resolving.rs +++ b/sixtyfps_compiler/passes/resolving.rs @@ -77,15 +77,26 @@ impl Expression { debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression); let e = node .child_node(SyntaxKind::Expression) + .map(|n| Self::from_expression_node(n, ctx)) .or_else(|| { - node.child_node(SyntaxKind::CodeBlock) - .and_then(|c| c.child_node(SyntaxKind::Expression)) + node.child_node(SyntaxKind::CodeBlock).map(|c| Self::from_codeblock_node(c, ctx)) }) - .map_or(Self::Invalid, |n| Self::from_expression_node(n, ctx)); + .unwrap_or(Self::Invalid); 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 { + debug_assert_eq!(node.kind(), SyntaxKind::Expression); node.child_node(SyntaxKind::Expression) .map(|n| Self::from_expression_node(n, ctx)) .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) } @@ -186,6 +208,18 @@ impl Expression { element: Rc::downgrade(&ctx.component.root_element), 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() { todo!("Continue lookling up"); } @@ -211,6 +245,18 @@ impl Expression { element: Rc::downgrade(&elem), 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 { ctx.diag.push_error( format!("Cannot access property '{}'", prop_name), @@ -225,20 +271,6 @@ impl Expression { 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) { let value: Option = match s { "blue" => Some(0xff0000ff), diff --git a/sixtyfps_compiler/tests/basic/signal.60 b/sixtyfps_compiler/tests/basic/signal.60 index 3aee81e08..e3bfde31d 100644 --- a/sixtyfps_compiler/tests/basic/signal.60 +++ b/sixtyfps_compiler/tests/basic/signal.60 @@ -3,7 +3,7 @@ SubElements := Rectangle { signal foobar; TouchArea { - clicked => { foobar } + clicked => { foobar() } } TouchArea { @@ -14,8 +14,8 @@ SubElements := Rectangle { } TouchArea { - clicked => { foobar } - clicked => { foobar } + clicked => { foobar() } + clicked => { foobar() } // ^error{Duplicated signal} } diff --git a/tools/viewer/eval.rs b/tools/viewer/eval.rs index 4f798a647..09bec38ff 100644 --- a/tools/viewer/eval.rs +++ b/tools/viewer/eval.rs @@ -4,6 +4,7 @@ use sixtyfps_compiler::typeregister::Type; #[derive(Debug)] pub enum Value { + Void, Number(f64), String(SharedString), } @@ -42,5 +43,13 @@ pub fn eval_expression( (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"), } }