Add compound assignment operators to language

703daa82a5b8d0034383204aee6f871b99ddc6757f4a53ecac13b6caba0d5721
Eg. `+=`, `/=`, etc.
Alexis Sellier committed ago 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
            }