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 // Expression parsing
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
macro_rules! bin_expr { macro_rules! bin_expr_right {
($name:ident, $op_tokens:expr, $next:ident) => { ($name:ident, $op_tokens:expr, $next:ident) => {
fn $name(&mut self) -> Result<Expr> { fn $name(&mut self) -> Result<Expr> {
let lhs = self.$next()?; 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> { impl<'t> Parser<'t> {
pub fn next_expr(&mut self) -> Result<Expr> { pub fn next_expr(&mut self) -> Result<Expr> {
self.next_bin_cmp_expr() self.next_bin_cmp_expr()
} }
// == < > <= >= // == < > <= >=
bin_expr!( bin_expr_right!(
next_bin_cmp_expr, next_bin_cmp_expr,
&[ &[
TokenKind::EqEq, TokenKind::EqEq,
@@ -176,19 +194,37 @@ impl<'t> Parser<'t> {
next_bin_add_expr next_bin_add_expr
); );
// + - // + -
bin_expr!( bin_expr_right!(
next_bin_add_expr, next_bin_add_expr,
&[TokenKind::Plus, TokenKind::Minus], &[TokenKind::Plus, TokenKind::Minus],
next_bin_mul_expr next_bin_mul_expr
); );
// * / // * /
bin_expr!( bin_expr_right!(
next_bin_mul_expr, next_bin_mul_expr,
&[TokenKind::FSlash, TokenKind::Splat], &[TokenKind::FSlash, TokenKind::Splat],
next_dot_expr 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> { fn next_un_expr(&mut self) -> Result<Expr> {
if let Some(un_op) = self.match_any_token_kind(&UN_EXPR_START)? { if let Some(un_op) = self.match_any_token_kind(&UN_EXPR_START)? {
@@ -304,7 +340,8 @@ impl<'t> Parser<'t> {
_ => unreachable!(), _ => 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 /// Takes an expression and attempts to match a suffix for it - either a function call or
@@ -974,17 +1011,20 @@ mod test {
test_parser!( test_parser!(
Parser::try_from("foo.bar().baz(qux)").unwrap(), Parser::try_from("foo.bar().baz(qux)").unwrap(),
next_expr, next_expr,
bin_expr(
base_expr(BaseExprKind::Ident),
BinOp::Dot,
bin_expr(
fun_call_expr(base_expr(BaseExprKind::Ident), vec![]),
BinOp::Dot,
fun_call_expr( fun_call_expr(
bin_expr(
fun_call_expr(
bin_expr(
base_expr(BaseExprKind::Ident), base_expr(BaseExprKind::Ident),
vec![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 // Stmt testing
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////