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 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<proc_macro2::TokenStream> {
fn compile_expression(e: &Expression) -> Option<proc_macro2::TokenStream> {
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})
}
})
}

View file

@ -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<int32> counter;
@ -84,7 +84,7 @@ Hello := Rectangle {
color: 4289374890;
x: 50;
y: 350;
clicked => { minus_clicked }
clicked => { minus_clicked() }
button_text: "-";
}

View file

@ -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: "-";
}

View file

@ -27,6 +27,12 @@ pub enum Expression {
/// Cast an expression to the given 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 {
@ -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),
}
}
}

View file

@ -162,9 +162,7 @@ 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 } => {
if matches!(item.lookup_property(s.as_str()), Type::Signal) {
let signal_accessor_prefix = if item.signals_declaration.contains(s) {
String::new()
} else {
@ -172,11 +170,10 @@ fn handle_item(
};
format!(
"{signal_accessor_prefix}{prop}.set_handler([](const void *root) {{ reinterpret_cast<const {ty}*>(root)->{fwd}.emit(root); }});",
signal_accessor_prefix = signal_accessor_prefix, prop = s, fwd = name.clone(), ty = main_struct.name
"{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, ty = main_struct.name, code = compile_expression(i)
)
}
_ => {
} else {
let accessor_prefix = if item.property_declarations.contains_key(s) {
String::new()
} else {
@ -191,7 +188,6 @@ fn handle_item(
init = init
)
}
}
}));
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);
let mut init = Vec::new();
let mut init = vec!["auto self = this;".into()];
handle_item(
&component.root_element.borrow(),
&declared_property_members,
@ -303,7 +299,23 @@ fn compile_expression(e: &crate::expression_tree::Expression) -> 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::<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 {
if self.signals_declaration.iter().any(|x| x == name) {
return Type::Signal;
}
self.property_declarations
.get(name)
.cloned()

View file

@ -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> {
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)
}
/// 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<Self::Checkpoint>,
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<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) {
@ -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)]

View file

@ -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);
}
_ => {}
}
}

View file

@ -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<u32> = match s {
"blue" => Some(0xff0000ff),

View file

@ -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}
}

View file

@ -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"),
}
}