Add compound assignment operators to language
703daa82a5b8d0034383204aee6f871b99ddc6757f4a53ecac13b6caba0d5721
Eg. `+=`, `/=`, etc.
1 parent
1cf980de
lib/std/arch/rv64/tests/compound.assign.field.rad
added
+26 -0
| 1 | + | /// Test compound assignment on record fields. |
|
| 2 | + | record Point { |
|
| 3 | + | x: i32, |
|
| 4 | + | y: i32, |
|
| 5 | + | } |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | let mut p = Point { x: 10, y: 20 }; |
|
| 9 | + | ||
| 10 | + | p.x += 5; |
|
| 11 | + | if p.x != 15 { |
|
| 12 | + | return 1; |
|
| 13 | + | } |
|
| 14 | + | ||
| 15 | + | p.y -= 3; |
|
| 16 | + | if p.y != 17 { |
|
| 17 | + | return 2; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | p.x *= 2; |
|
| 21 | + | if p.x != 30 { |
|
| 22 | + | return 3; |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | return 0; |
|
| 26 | + | } |
lib/std/arch/rv64/tests/compound.assign.rad
added
+92 -0
| 1 | + | /// Test compound assignment operators (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=). |
|
| 2 | + | @default fn main() -> i32 { |
|
| 3 | + | let mut a: i32 = 10; |
|
| 4 | + | ||
| 5 | + | // Test += |
|
| 6 | + | a += 5; |
|
| 7 | + | if a != 15 { |
|
| 8 | + | return 1; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | // Test -= |
|
| 12 | + | a -= 3; |
|
| 13 | + | if a != 12 { |
|
| 14 | + | return 2; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | // Test *= |
|
| 18 | + | a *= 2; |
|
| 19 | + | if a != 24 { |
|
| 20 | + | return 3; |
|
| 21 | + | } |
|
| 22 | + | ||
| 23 | + | // Test /= |
|
| 24 | + | a /= 4; |
|
| 25 | + | if a != 6 { |
|
| 26 | + | return 4; |
|
| 27 | + | } |
|
| 28 | + | ||
| 29 | + | // Test %= |
|
| 30 | + | a %= 4; |
|
| 31 | + | if a != 2 { |
|
| 32 | + | return 5; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | // Test &= |
|
| 36 | + | let mut b: i32 = 0xFF; |
|
| 37 | + | b &= 0x0F; |
|
| 38 | + | if b != 0x0F { |
|
| 39 | + | return 6; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | // Test |= |
|
| 43 | + | b |= 0xF0; |
|
| 44 | + | if b != 0xFF { |
|
| 45 | + | return 7; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | // Test ^= |
|
| 49 | + | b ^= 0x0F; |
|
| 50 | + | if b != 0xF0 { |
|
| 51 | + | return 8; |
|
| 52 | + | } |
|
| 53 | + | ||
| 54 | + | // Test <<= |
|
| 55 | + | let mut c: i32 = 1; |
|
| 56 | + | c <<= 4; |
|
| 57 | + | if c != 16 { |
|
| 58 | + | return 9; |
|
| 59 | + | } |
|
| 60 | + | ||
| 61 | + | // Test >>= |
|
| 62 | + | c >>= 2; |
|
| 63 | + | if c != 4 { |
|
| 64 | + | return 10; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | // Test compound assignment with array subscript. |
|
| 68 | + | let mut arr: [i32; 3] = [10, 20, 30]; |
|
| 69 | + | arr[1] += 5; |
|
| 70 | + | if arr[1] != 25 { |
|
| 71 | + | return 11; |
|
| 72 | + | } |
|
| 73 | + | ||
| 74 | + | // Test compound assignment with pointer dereference. |
|
| 75 | + | let mut val: i32 = 100; |
|
| 76 | + | let p: *mut i32 = &mut val; |
|
| 77 | + | *p += 50; |
|
| 78 | + | if val != 150 { |
|
| 79 | + | return 12; |
|
| 80 | + | } |
|
| 81 | + | ||
| 82 | + | // Test chained compound assignments. |
|
| 83 | + | let mut x: i32 = 1; |
|
| 84 | + | x += 2; |
|
| 85 | + | x *= 3; |
|
| 86 | + | x -= 1; |
|
| 87 | + | if x != 8 { |
|
| 88 | + | return 13; |
|
| 89 | + | } |
|
| 90 | + | ||
| 91 | + | return 0; |
|
| 92 | + | } |
lib/std/lang/lower/tests/compound.assign.rad
added
+6 -0
| 1 | + | /// Compound assignment with local variable. |
|
| 2 | + | fn compoundLocal(x: i32) -> i32 { |
|
| 3 | + | let mut a: i32 = x; |
|
| 4 | + | a += 10; |
|
| 5 | + | return a; |
|
| 6 | + | } |
lib/std/lang/lower/tests/compound.assign.ril
added
+5 -0
| 1 | + | fn w32 $compoundLocal(w32 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | add w32 %1 %0 10; |
|
| 4 | + | ret %1; |
|
| 5 | + | } |
lib/std/lang/parser.rad
+34 -0
| 836 | 836 | let left = try parseUnary(p); |
|
| 837 | 837 | let expr = try parseBinary(p, left, -1); |
|
| 838 | 838 | return try parseCondExpr(p, expr); |
|
| 839 | 839 | } |
|
| 840 | 840 | ||
| 841 | + | /// Try to consume a compound assignment operator and return its binary op. |
|
| 842 | + | fn tryCompoundAssignOp(p: *mut Parser) -> ?ast::BinaryOp { |
|
| 843 | + | match p.current.kind { |
|
| 844 | + | case scanner::TokenKind::PlusEqual => { advance(p); return ast::BinaryOp::Add; } |
|
| 845 | + | case scanner::TokenKind::MinusEqual => { advance(p); return ast::BinaryOp::Sub; } |
|
| 846 | + | case scanner::TokenKind::StarEqual => { advance(p); return ast::BinaryOp::Mul; } |
|
| 847 | + | case scanner::TokenKind::SlashEqual => { advance(p); return ast::BinaryOp::Div; } |
|
| 848 | + | case scanner::TokenKind::PercentEqual => { advance(p); return ast::BinaryOp::Mod; } |
|
| 849 | + | case scanner::TokenKind::AmpEqual => { advance(p); return ast::BinaryOp::BitAnd; } |
|
| 850 | + | case scanner::TokenKind::PipeEqual => { advance(p); return ast::BinaryOp::BitOr; } |
|
| 851 | + | case scanner::TokenKind::CaretEqual => { advance(p); return ast::BinaryOp::BitXor; } |
|
| 852 | + | case scanner::TokenKind::LtLtEqual => { advance(p); return ast::BinaryOp::Shl; } |
|
| 853 | + | case scanner::TokenKind::GtGtEqual => { advance(p); return ast::BinaryOp::Shr; } |
|
| 854 | + | else => return nil, |
|
| 855 | + | } |
|
| 856 | + | } |
|
| 857 | + | ||
| 841 | 858 | /// Parse an expression statement. |
|
| 842 | 859 | pub fn parseExprStmt(p: *mut Parser) -> *ast::Node |
|
| 843 | 860 | throws (ParseError) |
|
| 844 | 861 | { |
|
| 845 | 862 | let expr = try parseExpr(p); |
| 852 | 869 | ||
| 853 | 870 | return node(p, ast::NodeValue::Assign( |
|
| 854 | 871 | ast::Assign { left: expr, right: value } |
|
| 855 | 872 | )); |
|
| 856 | 873 | } |
|
| 874 | + | // Compound assignment: desugar `x <op>= y` into `x = x <op> y`. |
|
| 875 | + | // The left-hand side node is shared between the assignment target and the |
|
| 876 | + | // binary operand. This is safe because the resolver caches results by node |
|
| 877 | + | // and returns the same type on repeated visits. |
|
| 878 | + | if let op = tryCompoundAssignOp(p) { |
|
| 879 | + | if not isAssignableTarget(expr) { |
|
| 880 | + | throw failParsing(p, "invalid compound assignment target"); |
|
| 881 | + | } |
|
| 882 | + | let rhs = try parseExpr(p); |
|
| 883 | + | let binop = node(p, ast::NodeValue::BinOp( |
|
| 884 | + | ast::BinOp { op, left: expr, right: rhs } |
|
| 885 | + | )); |
|
| 886 | + | ||
| 887 | + | return node(p, ast::NodeValue::Assign( |
|
| 888 | + | ast::Assign { left: expr, right: binop } |
|
| 889 | + | )); |
|
| 890 | + | } |
|
| 857 | 891 | return node(p, ast::NodeValue::ExprStmt(expr)); |
|
| 858 | 892 | } |
|
| 859 | 893 | ||
| 860 | 894 | /// Parse leading attributes attached to the next declaration statement. |
|
| 861 | 895 | fn parseAttributes(p: *mut Parser) -> ?ast::Attributes { |
lib/std/lang/scanner.rad
+70 -6
| 52 | 52 | LtLt, // << |
|
| 53 | 53 | GtGt, // >> |
|
| 54 | 54 | Arrow, // -> |
|
| 55 | 55 | FatArrow, // => |
|
| 56 | 56 | ||
| 57 | + | // Compound assignment operators. |
|
| 58 | + | PlusEqual, // += |
|
| 59 | + | MinusEqual, // -= |
|
| 60 | + | StarEqual, // *= |
|
| 61 | + | SlashEqual, // /= |
|
| 62 | + | PercentEqual, // %= |
|
| 63 | + | AmpEqual, // &= |
|
| 64 | + | PipeEqual, // |= |
|
| 65 | + | CaretEqual, // ^= |
|
| 66 | + | LtLtEqual, // <<= |
|
| 67 | + | GtGtEqual, // >>= |
|
| 68 | + | ||
| 57 | 69 | // Boolean operators. |
|
| 58 | 70 | Not, And, Or, |
|
| 59 | 71 | ||
| 60 | 72 | /// Eg. `input:` |
|
| 61 | 73 | Label, |
| 134 | 146 | case TokenKind::LtEqual => return "LtEqual", |
|
| 135 | 147 | case TokenKind::LtLt => return "LtLt", |
|
| 136 | 148 | case TokenKind::GtGt => return "GtGt", |
|
| 137 | 149 | case TokenKind::Arrow => return "Arrow", |
|
| 138 | 150 | case TokenKind::FatArrow => return "FatArrow", |
|
| 151 | + | case TokenKind::PlusEqual => return "PlusEqual", |
|
| 152 | + | case TokenKind::MinusEqual => return "MinusEqual", |
|
| 153 | + | case TokenKind::StarEqual => return "StarEqual", |
|
| 154 | + | case TokenKind::SlashEqual => return "SlashEqual", |
|
| 155 | + | case TokenKind::PercentEqual => return "PercentEqual", |
|
| 156 | + | case TokenKind::AmpEqual => return "AmpEqual", |
|
| 157 | + | case TokenKind::PipeEqual => return "PipeEqual", |
|
| 158 | + | case TokenKind::CaretEqual => return "CaretEqual", |
|
| 159 | + | case TokenKind::LtLtEqual => return "LtLtEqual", |
|
| 160 | + | case TokenKind::GtGtEqual => return "GtGtEqual", |
|
| 139 | 161 | case TokenKind::Not => return "Not", |
|
| 140 | 162 | case TokenKind::And => return "And", |
|
| 141 | 163 | case TokenKind::Or => return "Or", |
|
| 142 | 164 | case TokenKind::Label => return "Label", |
|
| 143 | 165 | case TokenKind::Ident => return "Ident", |
| 553 | 575 | } |
|
| 554 | 576 | case '-' => { |
|
| 555 | 577 | if consume(s, '>') { |
|
| 556 | 578 | return tok(s, TokenKind::Arrow); |
|
| 557 | 579 | } |
|
| 580 | + | if consume(s, '=') { |
|
| 581 | + | return tok(s, TokenKind::MinusEqual); |
|
| 582 | + | } |
|
| 558 | 583 | // If followed by a digit, scan as negative number |
|
| 559 | 584 | if let ch = current(s); isDigit(ch) { |
|
| 560 | 585 | return scanNumber(s); |
|
| 561 | 586 | } |
|
| 562 | 587 | return tok(s, TokenKind::Minus); |
|
| 563 | 588 | } |
|
| 564 | 589 | case '+' => { |
|
| 590 | + | if consume(s, '=') { |
|
| 591 | + | return tok(s, TokenKind::PlusEqual); |
|
| 592 | + | } |
|
| 565 | 593 | if let ch = current(s); isDigit(ch) { |
|
| 566 | 594 | return scanNumber(s); |
|
| 567 | 595 | } |
|
| 568 | 596 | return tok(s, TokenKind::Plus); |
|
| 569 | 597 | } |
|
| 570 | - | case '/' => return tok(s, TokenKind::Slash), |
|
| 571 | - | case '*' => return tok(s, TokenKind::Star), |
|
| 572 | - | case '%' => return tok(s, TokenKind::Percent), |
|
| 573 | - | case '&' => return tok(s, TokenKind::Amp), |
|
| 598 | + | case '/' => { |
|
| 599 | + | if consume(s, '=') { |
|
| 600 | + | return tok(s, TokenKind::SlashEqual); |
|
| 601 | + | } |
|
| 602 | + | return tok(s, TokenKind::Slash); |
|
| 603 | + | } |
|
| 604 | + | case '*' => { |
|
| 605 | + | if consume(s, '=') { |
|
| 606 | + | return tok(s, TokenKind::StarEqual); |
|
| 607 | + | } |
|
| 608 | + | return tok(s, TokenKind::Star); |
|
| 609 | + | } |
|
| 610 | + | case '%' => { |
|
| 611 | + | if consume(s, '=') { |
|
| 612 | + | return tok(s, TokenKind::PercentEqual); |
|
| 613 | + | } |
|
| 614 | + | return tok(s, TokenKind::Percent); |
|
| 615 | + | } |
|
| 616 | + | case '&' => { |
|
| 617 | + | if consume(s, '=') { |
|
| 618 | + | return tok(s, TokenKind::AmpEqual); |
|
| 619 | + | } |
|
| 620 | + | return tok(s, TokenKind::Amp); |
|
| 621 | + | } |
|
| 574 | 622 | case '?' => return tok(s, TokenKind::Question), |
|
| 575 | - | case '|' => return tok(s, TokenKind::Pipe), |
|
| 576 | - | case '^' => return tok(s, TokenKind::Caret), |
|
| 623 | + | case '|' => { |
|
| 624 | + | if consume(s, '=') { |
|
| 625 | + | return tok(s, TokenKind::PipeEqual); |
|
| 626 | + | } |
|
| 627 | + | return tok(s, TokenKind::Pipe); |
|
| 628 | + | } |
|
| 629 | + | case '^' => { |
|
| 630 | + | if consume(s, '=') { |
|
| 631 | + | return tok(s, TokenKind::CaretEqual); |
|
| 632 | + | } |
|
| 633 | + | return tok(s, TokenKind::Caret); |
|
| 634 | + | } |
|
| 577 | 635 | case '~' => return tok(s, TokenKind::Tilde), |
|
| 578 | 636 | case '!' => { |
|
| 579 | 637 | if consume(s, '=') { |
|
| 580 | 638 | return tok(s, TokenKind::BangEqual); |
|
| 581 | 639 | } |
| 590 | 648 | } |
|
| 591 | 649 | return tok(s, TokenKind::Equal); |
|
| 592 | 650 | } |
|
| 593 | 651 | case '<' => { |
|
| 594 | 652 | if consume(s, '<') { |
|
| 653 | + | if consume(s, '=') { |
|
| 654 | + | return tok(s, TokenKind::LtLtEqual); |
|
| 655 | + | } |
|
| 595 | 656 | return tok(s, TokenKind::LtLt); |
|
| 596 | 657 | } |
|
| 597 | 658 | if consume(s, '=') { |
|
| 598 | 659 | return tok(s, TokenKind::LtEqual); |
|
| 599 | 660 | } |
|
| 600 | 661 | return tok(s, TokenKind::Lt); |
|
| 601 | 662 | } |
|
| 602 | 663 | case '>' => { |
|
| 603 | 664 | if consume(s, '>') { |
|
| 665 | + | if consume(s, '=') { |
|
| 666 | + | return tok(s, TokenKind::GtGtEqual); |
|
| 667 | + | } |
|
| 604 | 668 | return tok(s, TokenKind::GtGt); |
|
| 605 | 669 | } |
|
| 606 | 670 | if consume(s, '=') { |
|
| 607 | 671 | return tok(s, TokenKind::GtEqual); |
|
| 608 | 672 | } |