Update associativity of dot binary expressions and suffix expressions

The problem being encountered was, given something like this:

    foo.bar()

we would be getting an AST that looks like this:

    - binexpr:
        lhs: foo
        op: dot
        rhs:
            funcall:
                expr: bar
                params: []

which is the same as parsing like this:

    foo . (bar())

Which isn't what we want - it's like saying "get variable foo, and then
call function bar(), and whatever is returned by bar() should be used to
get an attribute from foo" and so forth. Instead, we want "get the
attribute bar from foo, and then call it as a function," which parses
like this:

    (foo . bar) ()

Dot expressions are now left-associative, and they also slurp function
call and index expression suffixes so that a dot expression before a
function call will be resolved before being called as a function.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-05-27 15:07:14 -04:00
parent a2a70a8604
commit 65b8d3af58

View File

@@ -142,7 +142,7 @@ impl<'t> Parser<'t> {
// Expression parsing
////////////////////////////////////////////////////////////////////////////////
macro_rules! bin_expr {
macro_rules! bin_expr_right {
($name:ident, $op_tokens:expr, $next:ident) => {
fn $name(&mut self) -> Result<Expr> {
let lhs = self.$next()?;
@@ -158,13 +158,31 @@ macro_rules! bin_expr {
};
}
/*
// left-associative rule - not currently used, but could be useful in the future
macro_rules! bin_expr_left {
($name:ident, $op_tokens:expr, $next:ident) => {
fn $name(&mut self) -> Result<Expr> {
let mut lhs = self.$next()?;
while let Some(token) = self.match_any_token_kind($op_tokens)? {
let op = BinOp::from(token);
let rhs = self.$next()?;
let span = lhs.span().union(rhs.span());
lhs = Expr::Bin(BinExpr { lhs, op, rhs, span }.into());
}
Ok(lhs)
}
};
}
*/
impl<'t> Parser<'t> {
pub fn next_expr(&mut self) -> Result<Expr> {
self.next_bin_cmp_expr()
}
// == < > <= >=
bin_expr!(
bin_expr_right!(
next_bin_cmp_expr,
&[
TokenKind::EqEq,
@@ -176,19 +194,37 @@ impl<'t> Parser<'t> {
next_bin_add_expr
);
// + -
bin_expr!(
bin_expr_right!(
next_bin_add_expr,
&[TokenKind::Plus, TokenKind::Minus],
next_bin_mul_expr
);
// * /
bin_expr!(
bin_expr_right!(
next_bin_mul_expr,
&[TokenKind::FSlash, TokenKind::Splat],
next_dot_expr
);
// .
bin_expr!(next_dot_expr, &[TokenKind::Dot], next_un_expr);
// Dot expressions also slurp function calls. This is so that something like:
//
// foo.bar().baz()
//
// will parse as
//
// ((foo.bar)().baz)()
//
fn next_dot_expr(&mut self) -> Result<Expr> {
let mut lhs = self.next_un_expr()?;
while let Some(token) = self.match_any_token_kind(&[TokenKind::Dot])? {
let op = BinOp::from(token);
let rhs = self.next_un_expr()?;
let span = lhs.span().union(rhs.span());
lhs = Expr::Bin(BinExpr { lhs, op, rhs, span }.into());
lhs = self.next_suffix_expr(lhs)?;
}
self.next_suffix_expr(lhs)
}
fn next_un_expr(&mut self) -> Result<Expr> {
if let Some(un_op) = self.match_any_token_kind(&UN_EXPR_START)? {
@@ -304,7 +340,8 @@ impl<'t> Parser<'t> {
_ => unreachable!(),
};
self.next_suffix_expr(expr)
//self.next_suffix_expr(expr)
Ok(expr)
}
/// Takes an expression and attempts to match a suffix for it - either a function call or
@@ -974,17 +1011,20 @@ mod test {
test_parser!(
Parser::try_from("foo.bar().baz(qux)").unwrap(),
next_expr,
bin_expr(
base_expr(BaseExprKind::Ident),
BinOp::Dot,
fun_call_expr(
bin_expr(
fun_call_expr(base_expr(BaseExprKind::Ident), vec![]),
BinOp::Dot,
fun_call_expr(
base_expr(BaseExprKind::Ident),
vec![base_expr(BaseExprKind::Ident)]
bin_expr(
base_expr(BaseExprKind::Ident),
BinOp::Dot,
base_expr(BaseExprKind::Ident)
),
vec![]
),
)
BinOp::Dot,
base_expr(BaseExprKind::Ident)
),
vec![base_expr(BaseExprKind::Ident)]
)
);
}
@@ -1161,6 +1201,31 @@ mod test {
);
}
#[test]
fn test_expr_left_assoc() {
test_parser! {
Parser::try_from(r"a.b.c.d").unwrap(),
next_expr,
bin_expr(
bin_expr(
bin_expr(
// a
base_expr(BaseExprKind::Ident),
BinOp::Dot,
// b
base_expr(BaseExprKind::Ident),
),
BinOp::Dot,
// c
base_expr(BaseExprKind::Ident),
),
BinOp::Dot,
// d
base_expr(BaseExprKind::Ident),
)
}
}
////////////////////////////////////////////////////////////////////////////////
// Stmt testing
////////////////////////////////////////////////////////////////////////////////