Parser: allow . to access member after any expression

Before we would only allow `foo.bar` where `foo` was an identifier.
Now we also allow more complex expression such as `"foo".bar` or `(123 + foo).bar`
(in the parser)
In particular, this will allow to get the member of objects returned by functions
or, in the future, part of arrays
This commit is contained in:
Olivier Goffart 2022-01-13 15:57:53 +01:00 committed by Olivier Goffart
parent 041a6d9710
commit b078fffc44
5 changed files with 56 additions and 14 deletions

View file

@ -359,7 +359,8 @@ declare_syntax! {
// FIXME: the test should test that as alternative rather than several of them (but it can also be a literal) // FIXME: the test should test that as alternative rather than several of them (but it can also be a literal)
Expression-> [ ?Expression, ?FunctionCallExpression, ?SelfAssignment, Expression-> [ ?Expression, ?FunctionCallExpression, ?SelfAssignment,
?ConditionalExpression, ?QualifiedName, ?BinaryExpression, ?Array, ?ObjectLiteral, ?ConditionalExpression, ?QualifiedName, ?BinaryExpression, ?Array, ?ObjectLiteral,
?UnaryOpExpression, ?CodeBlock, ?StringTemplate, ?AtImageUrl, ?AtLinearGradient], ?UnaryOpExpression, ?CodeBlock, ?StringTemplate, ?AtImageUrl, ?AtLinearGradient,
?MemberAccess ],
/// Concatenate the Expressions to make a string (usually expended from a template string) /// Concatenate the Expressions to make a string (usually expended from a template string)
StringTemplate -> [*Expression], StringTemplate -> [*Expression],
/// `@image-url("foo.png")` /// `@image-url("foo.png")`
@ -376,6 +377,8 @@ declare_syntax! {
BinaryExpression -> [2 Expression], BinaryExpression -> [2 Expression],
/// `- expr` /// `- expr`
UnaryOpExpression -> [Expression], UnaryOpExpression -> [Expression],
/// `(foo).bar`, where `foo` is the base expression, and `bar` is a Identifier.
MemberAccess -> [Expression],
/// `[ ... ]` /// `[ ... ]`
Array -> [ *Expression ], Array -> [ *Expression ],
/// `{ foo: bar }` /// `{ foo: bar }`

View file

@ -13,6 +13,7 @@ use super::prelude::*;
/// 42px /// 42px
/// #aabbcc /// #aabbcc
/// (something) /// (something)
/// (something).something
/// @image-url("something") /// @image-url("something")
/// @image_url("something") /// @image_url("something")
/// some_id.some_property /// some_id.some_property
@ -27,6 +28,7 @@ use super::prelude::*;
/// aa == cc && bb && (xxx || fff) && 3 + aaa == bbb /// aa == cc && bb && (xxx || fff) && 3 + aaa == bbb
/// [array] /// [array]
/// {object:42} /// {object:42}
/// "foo".bar.something().something.xx({a: 1.foo}.a)
/// ``` /// ```
pub fn parse_expression(p: &mut impl Parser) -> bool { pub fn parse_expression(p: &mut impl Parser) -> bool {
parse_expression_helper(p, OperatorPrecedence::Default) parse_expression_helper(p, OperatorPrecedence::Default)
@ -85,13 +87,28 @@ fn parse_expression_helper(p: &mut impl Parser, precedence: OperatorPrecedence)
} }
} }
if p.nth(0).kind() == SyntaxKind::LParent { loop {
match p.nth(0).kind() {
SyntaxKind::Dot => {
{
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
}
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::MemberAccess);
p.consume(); // '.'
if !p.expect(SyntaxKind::Identifier) {
return false;
}
}
SyntaxKind::LParent => {
{ {
let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression); let _ = p.start_node_at(checkpoint.clone(), SyntaxKind::Expression);
} }
let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression); let mut p = p.start_node_at(checkpoint.clone(), SyntaxKind::FunctionCallExpression);
parse_function_arguments(&mut *p); parse_function_arguments(&mut *p);
} }
_ => break,
}
}
if precedence >= OperatorPrecedence::Mul { if precedence >= OperatorPrecedence::Mul {
return true; return true;

View file

@ -287,6 +287,7 @@ impl Expression {
.or_else(|| { .or_else(|| {
node.FunctionCallExpression().map(|n| Self::from_function_call_node(n, ctx)) node.FunctionCallExpression().map(|n| Self::from_function_call_node(n, ctx))
}) })
.or_else(|| node.MemberAccess().map(|n| Self::from_member_access_node(n, ctx)))
.or_else(|| node.SelfAssignment().map(|n| Self::from_self_assignment_node(n, ctx))) .or_else(|| node.SelfAssignment().map(|n| Self::from_self_assignment_node(n, ctx)))
.or_else(|| node.BinaryExpression().map(|n| Self::from_binary_expression_node(n, ctx))) .or_else(|| node.BinaryExpression().map(|n| Self::from_binary_expression_node(n, ctx)))
.or_else(|| { .or_else(|| {
@ -591,22 +592,27 @@ impl Expression {
let mut sub_expr = node.Expression(); let mut sub_expr = node.Expression();
let function = sub_expr let function = sub_expr.next().map_or(Self::Invalid, |n| {
.next() // Treat the QualifiedName separately so we can catch the uses of uncalled signal
.and_then(|n| { n.QualifiedName()
n.QualifiedName().or_else(|| { .or_else(|| {
n.Expression().and_then(|mut e| { n.Expression().and_then(|mut e| {
while let Some(e2) = e.Expression() { while let Some(e2) = e.Expression() {
e = e2; e = e2;
} }
e.QualifiedName().map(|q| { e.QualifiedName().map(|q| {
ctx.diag.push_warning("Parentheses around callable are deprecated. Remove the parentheses".into(), &n); ctx.diag.push_warning(
"Parentheses around callable are deprecated. Remove the parentheses"
.into(),
&n,
);
q q
}) })
}) })
}) })
}) .map(|qn| Self::from_qualified_name_node(qn, ctx))
.map_or(Expression::Invalid, |n| Self::from_qualified_name_node(n, ctx)); .unwrap_or_else(|| Self::from_expression_node(n, ctx))
});
let sub_expr = sub_expr.map(|n| { let sub_expr = sub_expr.map(|n| {
(Self::from_expression_node(n.clone(), ctx), Some(NodeOrToken::from((*n).clone()))) (Self::from_expression_node(n.clone(), ctx), Some(NodeOrToken::from((*n).clone())))
@ -658,6 +664,14 @@ impl Expression {
} }
} }
fn from_member_access_node(
node: syntax_nodes::MemberAccess,
ctx: &mut LookupCtx,
) -> Expression {
let base = Self::from_expression_node(node.Expression(), ctx);
maybe_lookup_object(base, node.child_token(SyntaxKind::Identifier).into_iter(), ctx)
}
fn from_self_assignment_node( fn from_self_assignment_node(
node: syntax_nodes::SelfAssignment, node: syntax_nodes::SelfAssignment,
ctx: &mut LookupCtx, ctx: &mut LookupCtx,

View file

@ -17,6 +17,11 @@ TestCase := Rectangle {
foo.a = obj_conversion2.a; foo.a = obj_conversion2.a;
foo.b += 8 + obj_conversion2.b; foo.b += 8 + obj_conversion2.b;
} }
callback return_object() -> { aa: { bb: int } };
return_object => { return { aa: { bb: { cc: 42 }.cc } }; }
property <bool> test: return_object().aa.bb == 42 && obj_binop_merge;
} }
@ -30,6 +35,7 @@ assert_eq!(instance.get_foo_a(), sixtyfps::SharedString::from("hello"));
assert_eq!(instance.get_foo_b(), 20); assert_eq!(instance.get_foo_b(), 20);
assert_eq!(instance.get_obj_cond_merge_b(), 0); assert_eq!(instance.get_obj_cond_merge_b(), 0);
assert!(instance.get_obj_binop_merge()); assert!(instance.get_obj_binop_merge());
assert!(instance.get_test());
// This API to set with a tuple should maybe not be accessible? // This API to set with a tuple should maybe not be accessible?
instance.set_foo(("yo".into(), 33)); instance.set_foo(("yo".into(), 33));
@ -48,6 +54,7 @@ assert_eq(instance.get_foo_a(), sixtyfps::SharedString("hello"));
assert_eq(instance.get_foo_b(), 20); assert_eq(instance.get_foo_b(), 20);
assert_eq(instance.get_obj_cond_merge_b(), 0); assert_eq(instance.get_obj_cond_merge_b(), 0);
assert_eq(instance.get_obj_binop_merge(), true); assert_eq(instance.get_obj_binop_merge(), true);
assert(instance.get_test());
// This API to set with a tuple should maybe not be accessible? // This API to set with a tuple should maybe not be accessible?
instance.set_foo(std::make_tuple(sixtyfps::SharedString("yo"), 33)); instance.set_foo(std::make_tuple(sixtyfps::SharedString("yo"), 33));
@ -65,6 +72,7 @@ assert.equal(instance.foo_a, "hello");
assert.equal(instance.foo_b, 20); assert.equal(instance.foo_b, 20);
assert.equal(instance.obj_cond_merge_b, 0); assert.equal(instance.obj_cond_merge_b, 0);
assert(instance.obj_binop_merge); assert(instance.obj_binop_merge);
assert(instance.test);
instance.foo = { a: "yo", b: 33 }; instance.foo = { a: "yo", b: 33 };
assert.equal(instance.foo_a, "yo"); assert.equal(instance.foo_a, "yo");

View file

@ -12,7 +12,7 @@ TestCase := Rectangle {
property<bool> test_is_float: !hello.is_float() && number.is_float() && property<bool> test_is_float: !hello.is_float() && number.is_float() &&
!invalid.is_float() && negative.is_float(); !invalid.is_float() && negative.is_float();
property<bool> test: test_is_float && 42.56001 - number_as_float < 0.001; property<bool> test: test_is_float && 42.56001 - number_as_float < 0.001 && "123".to-float() == 123;
} }