From d499e866404678c44735635047aba27500d9417d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 13 Nov 2020 13:36:32 +0100 Subject: [PATCH] Implement cubic-bezier --- examples/slide_puzzle/slide_puzzle.60 | 2 +- sixtyfps_compiler/expression_tree.rs | 16 ++++ sixtyfps_compiler/generator/cpp.rs | 1 + sixtyfps_compiler/generator/rust.rs | 1 + sixtyfps_compiler/lib.rs | 2 + sixtyfps_compiler/passes/check_expressions.rs | 36 +++++++++ sixtyfps_compiler/passes/resolving.rs | 78 +++++++++++++++---- .../tests/syntax/basic/easing.60 | 29 +++++++ .../tests/syntax/basic/easing_not_called.60 | 17 ++++ .../tests/syntax/focus/focus_not_called.60 | 24 ++++++ .../tests/syntax/lookup/signal_arg.60 | 7 +- sixtyfps_runtime/interpreter/eval.rs | 1 + .../focus/focus_change_through_signal.60 | 2 +- 13 files changed, 198 insertions(+), 18 deletions(-) create mode 100644 sixtyfps_compiler/passes/check_expressions.rs create mode 100644 sixtyfps_compiler/tests/syntax/basic/easing.60 create mode 100644 sixtyfps_compiler/tests/syntax/basic/easing_not_called.60 create mode 100644 sixtyfps_compiler/tests/syntax/focus/focus_not_called.60 diff --git a/examples/slide_puzzle/slide_puzzle.60 b/examples/slide_puzzle/slide_puzzle.60 index 3a59170c53d..6a80e1a5beb 100644 --- a/examples/slide_puzzle/slide_puzzle.60 +++ b/examples/slide_puzzle/slide_puzzle.60 @@ -200,7 +200,7 @@ export MainWindow := Window { + (parent.width - (4*pieces_size + 3*pieces_spacing))/2; y: px * (pieces_size + pieces_spacing) + (parent.height - (4*pieces_size + 3*pieces_spacing))/2; - animate px , py { duration: 100ms; easing: ease-out; } + animate px , py { duration: 200ms; easing: cubic-bezier(0.17,0.76,0.4,1.9); } animate border-width, border-radius { duration: 500ms; easing: ease-out; } Text { diff --git a/sixtyfps_compiler/expression_tree.rs b/sixtyfps_compiler/expression_tree.rs index 2d0e1677307..5567738e256 100644 --- a/sixtyfps_compiler/expression_tree.rs +++ b/sixtyfps_compiler/expression_tree.rs @@ -56,6 +56,14 @@ pub enum BuiltinFunction { StringIsFloat, } +#[derive(Debug, Clone)] +/// A builtin function which is handled by the compiler pass +pub enum BuiltinMacroFunction { + Min, + Max, + CubicBezier, +} + impl BuiltinFunction { pub fn ty(&self) -> Type { match self { @@ -214,6 +222,10 @@ pub enum Expression { member: Box, }, + /// Reference to a macro understood by the compiler. + /// These should be transformed to other expression before reaching generation + BuiltinMacroReference(BuiltinMacroFunction, NodeOrTokenWithSourceFile), + /// A reference to a specific element. This isn't possible to create in .60 syntax itself, but intermediate passes may generate this /// type of expression. ElementReference(Weak>), @@ -350,6 +362,7 @@ impl Expression { } Expression::BuiltinFunctionReference(funcref) => funcref.ty(), Expression::MemberFunction { member, .. } => member.ty(), + Expression::BuiltinMacroReference { .. } => Type::Invalid, // We don't know the type Expression::ElementReference(_) => Type::ElementReference, Expression::RepeaterIndexReference { .. } => Type::Int32, Expression::RepeaterModelReference { element } => { @@ -448,6 +461,7 @@ impl Expression { visitor(&**base); visitor(&**member); } + Expression::BuiltinMacroReference { .. } => {} Expression::ElementReference(_) => {} Expression::ObjectAccess { base, .. } => visitor(&**base), Expression::RepeaterIndexReference { .. } => {} @@ -517,6 +531,7 @@ impl Expression { visitor(&mut **base); visitor(&mut **member); } + Expression::BuiltinMacroReference { .. } => {} Expression::ElementReference(_) => {} Expression::ObjectAccess { base, .. } => visitor(&mut **base), Expression::RepeaterIndexReference { .. } => {} @@ -584,6 +599,7 @@ impl Expression { Expression::RepeaterIndexReference { .. } => false, Expression::RepeaterModelReference { .. } => false, Expression::FunctionParameterReference { .. } => false, + Expression::BuiltinMacroReference { .. } => false, Expression::ObjectAccess { base, .. } => base.is_constant(), Expression::Cast { from, to } => { from.is_constant() && !matches!(to, Type::Length | Type::LogicalLength) diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index be8293499cd..49b08c42e01 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -1335,6 +1335,7 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc todo!("Element references are only supported in the context of built-in function calls at the moment"), Expression::MemberFunction { .. } => panic!("member function expressions must not appear in the code generator anymore"), + Expression::BuiltinMacroReference { .. } => panic!("macro expressions must not appear in the code generator anymore"), Expression::RepeaterIndexReference { element } => { let access = access_member( &element.upgrade().unwrap().borrow().base_type.as_component().root_element, diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index 5e03c0bb2d7..c982100aa94 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -1067,6 +1067,7 @@ fn compile_expression(e: &Expression, component: &Rc) -> TokenStream }, Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"), Expression::MemberFunction{ .. } => panic!("member function expressions must not appear in the code generator anymore"), + Expression::BuiltinMacroReference { .. } => panic!("macro expressions must not appear in the code generator anymore"), Expression::RepeaterIndexReference { element } => { let access = access_member( &element.upgrade().unwrap().borrow().base_type.as_component().root_element, diff --git a/sixtyfps_compiler/lib.rs b/sixtyfps_compiler/lib.rs index 7a93775222f..d954b39be2b 100644 --- a/sixtyfps_compiler/lib.rs +++ b/sixtyfps_compiler/lib.rs @@ -39,6 +39,7 @@ pub mod typeloader; pub mod typeregister; mod passes { + pub mod check_expressions; pub mod collect_globals; pub mod collect_resources; pub mod compile_paths; @@ -147,6 +148,7 @@ pub async fn run_passes<'a>( ) { passes::resolving::resolve_expressions(doc, diag); passes::inlining::inline(doc); + passes::check_expressions::check_expressions(doc, diag); passes::compile_paths::compile_paths(&doc.root_component, &doc.local_registry, diag); passes::unique_id::assign_unique_id(&doc.root_component); passes::focus_item::determine_initial_focus_item(&doc.root_component, diag); diff --git a/sixtyfps_compiler/passes/check_expressions.rs b/sixtyfps_compiler/passes/check_expressions.rs new file mode 100644 index 00000000000..aab6dfac7fc --- /dev/null +++ b/sixtyfps_compiler/passes/check_expressions.rs @@ -0,0 +1,36 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +use crate::diagnostics::BuildDiagnostics; +use crate::expression_tree::Expression; +use crate::object_tree::{recurse_elem, visit_element_expressions}; + +/// Check the validity of expressions +/// +/// - Make sure that there is no uncalled member function or macro +pub fn check_expressions(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) { + for component in &doc.inner_components { + recurse_elem(&component.root_element, &(), &mut |elem, _| { + visit_element_expressions(elem, |e, _, _| check_expression(e, diag)); + }) + } +} + +fn check_expression(e: &Expression, diag: &mut BuildDiagnostics) -> () { + match e { + Expression::MemberFunction { base_node, .. } => { + diag.push_error("Member function must be called".into(), base_node); + } + Expression::BuiltinMacroReference(_, node) => { + diag.push_error("Builtin function must be called".into(), node); + } + _ => e.visit(|e| check_expression(e, diag)), + } +} diff --git a/sixtyfps_compiler/passes/resolving.rs b/sixtyfps_compiler/passes/resolving.rs index 7f533cc7798..d19388ffe15 100644 --- a/sixtyfps_compiler/passes/resolving.rs +++ b/sixtyfps_compiler/passes/resolving.rs @@ -18,7 +18,9 @@ use crate::diagnostics::BuildDiagnostics; use crate::expression_tree::*; use crate::langtype::Type; use crate::object_tree::*; -use crate::parser::{identifier_text, syntax_nodes, SyntaxKind, SyntaxNodeWithSourceFile}; +use crate::parser::{ + identifier_text, syntax_nodes, NodeOrTokenWithSourceFile, SyntaxKind, SyntaxNodeWithSourceFile, +}; use crate::typeregister::TypeRegister; use std::{collections::HashMap, rc::Rc}; @@ -481,7 +483,12 @@ impl Expression { "ease_in" => Some(EasingCurve::CubicBezier(0.42, 0.0, 1.0, 1.0)), "ease_in_out" => Some(EasingCurve::CubicBezier(0.42, 0.0, 0.58, 1.0)), "ease_out" => Some(EasingCurve::CubicBezier(0.0, 0.0, 0.58, 1.0)), - "cubic_bezier" => todo!("Not yet implemented"), + "cubic_bezier" => { + return Expression::BuiltinMacroReference( + BuiltinMacroFunction::CubicBezier, + first.into(), + ) + } _ => None, }; if let Some(curve) = value { @@ -497,9 +504,16 @@ impl Expression { } // Builtin functions FIXME: handle that in a registery or something - if first_str == "debug" { - return Expression::BuiltinFunctionReference(BuiltinFunction::Debug); - } + match first_str.as_str() { + "debug" => return Expression::BuiltinFunctionReference(BuiltinFunction::Debug), + "max" => { + return Expression::BuiltinMacroReference(BuiltinMacroFunction::Max, first.into()) + } + "min" => { + return Expression::BuiltinMacroReference(BuiltinMacroFunction::Min, first.into()) + } + _ => {} + }; // Attempt to recover if the user wanted to write "-" if let Some(minus_pos) = first.text().find('-') { @@ -532,19 +546,55 @@ impl Expression { node: syntax_nodes::FunctionCallExpression, ctx: &mut LookupCtx, ) -> Expression { - let mut sub_expr = - node.Expression().map(|n| (Self::from_expression_node(n.clone(), ctx), n.0.into())); + let mut sub_expr = node.Expression().map(|n| { + (Self::from_expression_node(n.clone(), ctx), NodeOrTokenWithSourceFile::from(n.0)) + }); let mut arguments = Vec::new(); - let function = sub_expr.next().map_or(Expression::Invalid, |e| e.0); - let function = if let Expression::MemberFunction { base, base_node, member } = function { - arguments.push((*base, base_node)); - member - } else { - Box::new(function) - }; + let (function, f_node) = + sub_expr.next().unwrap_or_else(|| (Expression::Invalid, node.0.clone().into())); + + let function = match function { + Expression::BuiltinMacroReference(mac, _) => match mac { + BuiltinMacroFunction::Min => todo!("min/max not yet implemented"), + BuiltinMacroFunction::Max => todo!("min/max not yet implemented"), + BuiltinMacroFunction::CubicBezier => { + let mut has_error = None; + // FIXME: this is not pretty to be handling there. + // Maybe "cubic_bezier" should be a function that is lowered later + let mut a = || match sub_expr.next() { + None => { + has_error.get_or_insert((f_node.clone(), "Not enough arguments")); + 0. + } + Some((Expression::NumberLiteral(val, Unit::None), _)) => val as f32, + Some((_, n)) => { + has_error.get_or_insert(( + n, + "Arguments to cubic bezier curve must be number literal", + )); + 0. + } + }; + let expr = + Expression::EasingCurve(EasingCurve::CubicBezier(a(), a(), a(), a())); + if let Some((_, n)) = sub_expr.next() { + has_error.get_or_insert((n, "Too many argument for bezier curve")); + } + if let Some((n, msg)) = has_error { + ctx.diag.push_error(msg.into(), &n); + } + return expr; + } + }, + Expression::MemberFunction { base, base_node, member } => { + arguments.push((*base, base_node)); + member + } + _ => Box::new(function), + }; arguments.extend(sub_expr); let arguments = match function.ty() { diff --git a/sixtyfps_compiler/tests/syntax/basic/easing.60 b/sixtyfps_compiler/tests/syntax/basic/easing.60 new file mode 100644 index 00000000000..eefefbbbd1b --- /dev/null +++ b/sixtyfps_compiler/tests/syntax/basic/easing.60 @@ -0,0 +1,29 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +X := Rectangle { + animate x { easing: ease-in; } + animate y { easing: foo; } + // ^error{Unknown unqualified identifier 'foo'} + animate color { easing: a; } + // ^error{Cannot convert int to easing} + property a; animate a { easing: cubic-bezier(0.01,1.46,0.94,1.37); } + property b; animate b { easing: cubic-bezier(0.01,1.46,0.94); } + // ^error{Not enough arguments} + property c; animate c { easing: cubic-bezier(); } + // ^error{Not enough arguments} + property d; animate d { easing: cubic-bezier(0,0,0,0,0,0); } + // ^error{Too many argument for bezier curve} + property e; animate e { easing: cubic-bezier(0, a, b, c); } + // ^error{Arguments to cubic bezier curve must be number literal} + property f; animate f { easing: cubic-bezier(0,0+0,0,0,0); } + // ^error{Arguments to cubic bezier curve must be number literal} +} + diff --git a/sixtyfps_compiler/tests/syntax/basic/easing_not_called.60 b/sixtyfps_compiler/tests/syntax/basic/easing_not_called.60 new file mode 100644 index 00000000000..9b3b3933862 --- /dev/null +++ b/sixtyfps_compiler/tests/syntax/basic/easing_not_called.60 @@ -0,0 +1,17 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +// Cannot be put in the easing.60 test because it is in a different pass + +X := Rectangle { + property g; animate g { easing: cubic-bezier; } + // ^error{must be called} +} + diff --git a/sixtyfps_compiler/tests/syntax/focus/focus_not_called.60 b/sixtyfps_compiler/tests/syntax/focus/focus_not_called.60 new file mode 100644 index 00000000000..b5ec5f5e457 --- /dev/null +++ b/sixtyfps_compiler/tests/syntax/focus/focus_not_called.60 @@ -0,0 +1,24 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + + +X := Rectangle { + edit := TextInput { } + TouchArea { + clicked => { + (edit.focus)(); + edit.focus; +// ^error{Member function must be called} + } + } + x: edit.focus; +// ^error{Cannot convert function\(element ref\) -> void to length} +// ^^error{Member function must be called} +} \ No newline at end of file diff --git a/sixtyfps_compiler/tests/syntax/lookup/signal_arg.60 b/sixtyfps_compiler/tests/syntax/lookup/signal_arg.60 index 18836560b7a..b42fee0fc03 100644 --- a/sixtyfps_compiler/tests/syntax/lookup/signal_arg.60 +++ b/sixtyfps_compiler/tests/syntax/lookup/signal_arg.60 @@ -25,10 +25,13 @@ Xxx := Rectangle { // ^error{The signal or function expects 3 arguments, but 4 are provided} plop(42, 42, 42); // ^error{Cannot convert float to color} - hello(45, fff) + hello(45, fff); // ^error{The expression is not a function} // ^^error{Unknown unqualified identifier 'fff'} - + (plop)("45", #fff, 42); + (root.plop)("45", #fff, 42); + (root.plop)("45", #fff, "45"); +// ^error{Cannot convert string to int} } x: 12phx; diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index e1bc4a2033d..ac5049c596b 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -270,6 +270,7 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) -> ), Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"), Expression::MemberFunction { .. } => panic!("member function expressions must not appear in the code generator anymore"), + Expression::BuiltinMacroReference { .. } => panic!("macro expressions must not appear in the code generator anymore"), Expression::PropertyReference(NamedReference { element, name }) => { load_property_helper(local_context.component_instance, &element.upgrade().unwrap(), name.as_ref()).unwrap() } diff --git a/tests/cases/focus/focus_change_through_signal.60 b/tests/cases/focus/focus_change_through_signal.60 index 4d10f39823c..b8950e8f212 100644 --- a/tests/cases/focus/focus_change_through_signal.60 +++ b/tests/cases/focus/focus_change_through_signal.60 @@ -15,7 +15,7 @@ TestCase := Rectangle { focus_input1 => { input1.focus(); } signal focus_input2(); - focus_input2 => { input2.focus(); } + focus_input2 => { (input2.focus)(); } input1 := TextInput { width: parent.width;