diff --git a/compiler/qsc_parse/src/expr.rs b/compiler/qsc_parse/src/expr.rs index 433dcf2d55..5647d7bbcc 100644 --- a/compiler/qsc_parse/src/expr.rs +++ b/compiler/qsc_parse/src/expr.rs @@ -43,6 +43,9 @@ struct MixfixOp { enum OpKind { Postfix(UnOp), Binary(BinOp, Assoc), + Assign, + AssignUpdate, + AssignBinary(BinOp), Ternary(TernOp, TokenKind, Assoc), Rich(fn(&mut ParserContext, Box) -> Result>), } @@ -131,6 +134,20 @@ fn expr_op(s: &mut ParserContext, context: OpContext) -> Result> { s.advance(); let kind = match op.kind { OpKind::Postfix(kind) => Box::new(ExprKind::UnOp(kind, lhs)), + OpKind::Assign => { + let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; + Box::new(ExprKind::Assign(lhs, rhs)) + } + OpKind::AssignUpdate => { + let mid = expr(s)?; + token(s, TokenKind::LArrow)?; + let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; + Box::new(ExprKind::AssignUpdate(lhs, mid, rhs)) + } + OpKind::AssignBinary(kind) => { + let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; + Box::new(ExprKind::AssignOp(kind, lhs, rhs)) + } OpKind::Binary(kind, assoc) => { let precedence = next_precedence(op.precedence, assoc); let rhs = expr_op(s, OpContext::Precedence(precedence))?; @@ -212,7 +229,17 @@ fn expr_base(s: &mut ParserContext) -> Result> { } else if token(s, TokenKind::Keyword(Keyword::Return)).is_ok() { Ok(Box::new(ExprKind::Return(expr(s)?))) } else if token(s, TokenKind::Keyword(Keyword::Set)).is_ok() { - expr_set(s) + // Need to rewrite the span of the expr to include the `set` keyword. + return expr(s).map(|assign| { + Box::new(Expr { + id: assign.id, + span: Span { + lo, + hi: assign.span.hi, + }, + kind: assign.kind, + }) + }); } else if token(s, TokenKind::Keyword(Keyword::While)).is_ok() { Ok(Box::new(ExprKind::While(expr(s)?, stmt::parse_block(s)?))) } else if token(s, TokenKind::Keyword(Keyword::Within)).is_ok() { @@ -357,8 +384,8 @@ fn expr_array_core(s: &mut ParserContext) -> Result> { s.expect(WordKinds::Size); let second = expr(s)?; - if is_ident("size", &second.kind) && token(s, TokenKind::Eq).is_ok() { - let size = expr(s)?; + if let Some(size) = is_array_size(&second.kind) { + let size = Box::new(size.clone()); return Ok(Box::new(ExprKind::ArrayRepeat(first, size))); } @@ -369,8 +396,18 @@ fn expr_array_core(s: &mut ParserContext) -> Result> { Ok(Box::new(ExprKind::Array(items.into_boxed_slice()))) } -fn is_ident(name: &str, kind: &ExprKind) -> bool { - matches!(kind, ExprKind::Path(PathKind::Ok(path)) if path.segments.is_none() && path.name.name.as_ref() == name) +fn is_array_size(kind: &ExprKind) -> Option<&Expr> { + match kind { + ExprKind::Assign(lhs, rhs) => match lhs.kind.as_ref() { + ExprKind::Path(PathKind::Ok(path)) + if path.segments.is_none() && path.name.name.as_ref() == "size" => + { + Some(rhs) + } + _ => None, + }, + _ => None, + } } fn expr_range_prefix(s: &mut ParserContext) -> Result> { @@ -572,6 +609,18 @@ fn prefix_op(name: OpName) -> Option { #[allow(clippy::too_many_lines)] fn mixfix_op(name: OpName) -> Option { match name { + OpName::Token(TokenKind::Eq) => Some(MixfixOp { + kind: OpKind::Assign, + precedence: 0, + }), + OpName::Token(TokenKind::WSlashEq) => Some(MixfixOp { + kind: OpKind::AssignUpdate, + precedence: 0, + }), + OpName::Token(TokenKind::BinOpEq(kind)) => Some(MixfixOp { + kind: OpKind::AssignBinary(closed_bin_op(kind)), + precedence: 0, + }), OpName::Token(TokenKind::RArrow) => Some(MixfixOp { kind: OpKind::Rich(|s, input| lambda_op(s, *input, CallableKind::Function)), precedence: LAMBDA_PRECEDENCE, diff --git a/compiler/qsc_parse/src/expr/tests.rs b/compiler/qsc_parse/src/expr/tests.rs index 18069d761b..2389c5d305 100644 --- a/compiler/qsc_parse/src/expr/tests.rs +++ b/compiler/qsc_parse/src/expr/tests.rs @@ -5,6 +5,20 @@ use super::expr; use crate::tests::check; use expect_test::expect; +#[test] +fn foo() { + check( + expr, + "x = j = 3", + &expect![[r#" + Expr _id_ [0-9]: Assign: + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [4-9]: Assign: + Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "j") + Expr _id_ [8-9]: Lit: Int(3)"#]], + ); +} + #[test] fn lit_int() { check(expr, "123", &expect!["Expr _id_ [0-3]: Lit: Int(123)"]); @@ -699,6 +713,14 @@ fn set() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [8-9]: Path: Path _id_ [8-9] (Ident _id_ [8-9] "y")"#]], ); + check( + expr, + "x = y", + &expect![[r#" + Expr _id_ [0-5]: Assign: + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "y")"#]], + ); } #[test] @@ -711,6 +733,14 @@ fn set_hole() { Expr _id_ [4-5]: Hole Expr _id_ [8-9]: Lit: Int(1)"#]], ); + check( + expr, + "_ = 1", + &expect![[r#" + Expr _id_ [0-5]: Assign: + Expr _id_ [0-1]: Hole + Expr _id_ [4-5]: Lit: Int(1)"#]], + ); } #[test] @@ -727,6 +757,18 @@ fn set_hole_tuple() { Expr _id_ [14-15]: Lit: Int(1) Expr _id_ [17-18]: Lit: Int(2)"#]], ); + check( + expr, + "(x, _) = (1, 2)", + &expect![[r#" + Expr _id_ [0-15]: Assign: + Expr _id_ [0-6]: Tuple: + Expr _id_ [1-2]: Path: Path _id_ [1-2] (Ident _id_ [1-2] "x") + Expr _id_ [4-5]: Hole + Expr _id_ [9-15]: Tuple: + Expr _id_ [10-11]: Lit: Int(1) + Expr _id_ [13-14]: Lit: Int(2)"#]], + ); } #[test] @@ -747,6 +789,22 @@ fn set_hole_tuple_nested() { Expr _id_ [23-24]: Lit: Int(2) Expr _id_ [26-27]: Lit: Int(3)"#]], ); + check( + expr, + "(_, (x, _)) = (1, (2, 3))", + &expect![[r#" + Expr _id_ [0-25]: Assign: + Expr _id_ [0-11]: Tuple: + Expr _id_ [1-2]: Hole + Expr _id_ [4-10]: Tuple: + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "x") + Expr _id_ [8-9]: Hole + Expr _id_ [14-25]: Tuple: + Expr _id_ [15-16]: Lit: Int(1) + Expr _id_ [18-24]: Tuple: + Expr _id_ [19-20]: Lit: Int(2) + Expr _id_ [22-23]: Lit: Int(3)"#]], + ); } #[test] @@ -759,6 +817,14 @@ fn set_bitwise_and() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x &&&= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (AndB): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -771,6 +837,14 @@ fn set_logical_and() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x and= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (AndL): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -783,6 +857,14 @@ fn set_bitwise_or() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x |||= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (OrB): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -795,6 +877,14 @@ fn set_exp() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x ^= y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Exp): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -807,6 +897,14 @@ fn set_bitwise_xor() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x ^^^= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (XorB): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -819,6 +917,14 @@ fn set_shr() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x >>>= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (Shr): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -831,6 +937,14 @@ fn set_shl() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], ); + check( + expr, + "x <<<= y", + &expect![[r#" + Expr _id_ [0-8]: AssignOp (Shl): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]], + ); } #[test] @@ -843,6 +957,14 @@ fn set_sub() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x -= y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Sub): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -855,6 +977,14 @@ fn set_logical_or() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [10-11]: Path: Path _id_ [10-11] (Ident _id_ [10-11] "y")"#]], ); + check( + expr, + "x or= y", + &expect![[r#" + Expr _id_ [0-7]: AssignOp (OrL): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [6-7]: Path: Path _id_ [6-7] (Ident _id_ [6-7] "y")"#]], + ); } #[test] @@ -867,6 +997,14 @@ fn set_mod() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x %= y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Mod): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -879,6 +1017,14 @@ fn set_add() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x += y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Add): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -891,6 +1037,14 @@ fn set_div() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x /= y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Div): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -903,6 +1057,14 @@ fn set_mul() { Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x") Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]], ); + check( + expr, + "x *= y", + &expect![[r#" + Expr _id_ [0-6]: AssignOp (Mul): + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]], + ); } #[test] @@ -916,6 +1078,15 @@ fn set_with_update() { Expr _id_ [10-11]: Path: Path _id_ [10-11] (Ident _id_ [10-11] "i") Expr _id_ [15-16]: Path: Path _id_ [15-16] (Ident _id_ [15-16] "y")"#]], ); + check( + expr, + "x w/= i <- y", + &expect![[r#" + Expr _id_ [0-12]: AssignUpdate: + Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x") + Expr _id_ [6-7]: Path: Path _id_ [6-7] (Ident _id_ [6-7] "i") + Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]], + ); } #[test] @@ -1073,19 +1244,10 @@ fn array_repeat_no_items() { expr, "[size = 3]", &expect![[r#" - Error( - Token( - Close( - Bracket, - ), - Eq, - Span { - lo: 6, - hi: 7, - }, - ), - ) - "#]], + Expr _id_ [0-10]: Array: + Expr _id_ [1-9]: Assign: + Expr _id_ [1-5]: Path: Path _id_ [1-5] (Ident _id_ [1-5] "size") + Expr _id_ [8-9]: Lit: Int(3)"#]], ); } @@ -1095,19 +1257,12 @@ fn array_repeat_two_items() { expr, "[1, 2, size = 3]", &expect![[r#" - Error( - Token( - Close( - Bracket, - ), - Eq, - Span { - lo: 12, - hi: 13, - }, - ), - ) - "#]], + Expr _id_ [0-16]: Array: + Expr _id_ [1-2]: Lit: Int(1) + Expr _id_ [4-5]: Lit: Int(2) + Expr _id_ [7-15]: Assign: + Expr _id_ [7-11]: Path: Path _id_ [7-11] (Ident _id_ [7-11] "size") + Expr _id_ [14-15]: Lit: Int(3)"#]], ); } @@ -2496,6 +2651,30 @@ fn duplicate_commas_in_pattern() { ), ]"#]], ); + check( + expr, + "(x,, y) = (1, 2)", + &expect![[r#" + Expr _id_ [0-16]: Assign: + Expr _id_ [0-7]: Tuple: + Expr _id_ [1-2]: Path: Path _id_ [1-2] (Ident _id_ [1-2] "x") + Expr _id_ [3-3]: Err + Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y") + Expr _id_ [10-16]: Tuple: + Expr _id_ [11-12]: Lit: Int(1) + Expr _id_ [14-15]: Lit: Int(2) + + [ + Error( + MissingSeqEntry( + Span { + lo: 3, + hi: 3, + }, + ), + ), + ]"#]], + ); } #[test] @@ -2523,6 +2702,29 @@ fn invalid_initial_commas_in_pattern() { ), ]"#]], ); + check( + expr, + "(, x) = (1, 2)", + &expect![[r#" + Expr _id_ [0-14]: Assign: + Expr _id_ [0-5]: Tuple: + Expr _id_ [1-1]: Err + Expr _id_ [3-4]: Path: Path _id_ [3-4] (Ident _id_ [3-4] "x") + Expr _id_ [8-14]: Tuple: + Expr _id_ [9-10]: Lit: Int(1) + Expr _id_ [12-13]: Lit: Int(2) + + [ + Error( + MissingSeqEntry( + Span { + lo: 1, + hi: 1, + }, + ), + ), + ]"#]], + ); } #[test]