Skip to content

Commit 934ad56

Browse files
Assignment operators (&=, |=, +=, -=, *=, /=, %=, >>=, <<=) (#2301)
<!-- ELLIPSIS_HIDDEN --> > [!IMPORTANT] > Add support for assignment operators (`+=`, `-=`, etc.) in BAML, updating compiler, parser, and VM with tests. > > - **Behavior**: > - Adds support for assignment operators (`+=`, `-=`, `*=`, `/=`, `%=`) in `codegen.rs` by introducing `AssignOp` handling in `compile_statement()`. > - Updates `typecheck_statement()` in `typecheck.rs` to handle `AssignOp` for type checking. > - Extends `Stmt` enum in `stmt.rs` to include `AssignOpStmt`. > - **Parser**: > - Updates `parse_expr.rs` to parse `AssignOpStmt`. > - Modifies `datamodel.pest` to include grammar rules for assignment operators. > - **HIR**: > - Adds `AssignOp` to `Statement` enum in `mod.rs`. > - Implements `AssignOp` handling in `lowering.rs` for AST to HIR conversion. > - **Tests**: > - Adds tests in `vm.rs` to verify execution of assignment operators. > - Includes tests for each operator (`+=`, `-=`, `*=`, `/=`, `%=`) to ensure correct VM execution. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 08330af. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 9bd9552 commit 934ad56

11 files changed

Lines changed: 390 additions & 17 deletions

File tree

engine/baml-compiler/src/codegen.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,37 @@ impl<'g> HirCompiler<'g> {
298298
self.track_local(name);
299299
}
300300

301-
hir::Statement::Assign { name, value } => {
301+
hir::Statement::Assign { name, value, .. } => {
302302
self.compile_expression(value);
303303
self.emit(Instruction::StoreVar(self.locals[name]));
304304
}
305305

306+
hir::Statement::AssignOp {
307+
name,
308+
value,
309+
assign_op,
310+
..
311+
} => {
312+
self.emit(Instruction::LoadVar(self.locals[name]));
313+
self.compile_expression(value);
314+
315+
self.emit(match assign_op {
316+
hir::AssignOp::AddAssign => Instruction::BinOp(BinOp::Add),
317+
hir::AssignOp::SubAssign => Instruction::BinOp(BinOp::Sub),
318+
hir::AssignOp::MulAssign => Instruction::BinOp(BinOp::Mul),
319+
hir::AssignOp::DivAssign => Instruction::BinOp(BinOp::Div),
320+
hir::AssignOp::ModAssign => Instruction::BinOp(BinOp::Mod),
321+
322+
hir::AssignOp::BitAndAssign => Instruction::BinOp(BinOp::BitAnd),
323+
hir::AssignOp::BitOrAssign => Instruction::BinOp(BinOp::BitOr),
324+
hir::AssignOp::BitXorAssign => Instruction::BinOp(BinOp::BitXor),
325+
hir::AssignOp::ShlAssign => Instruction::BinOp(BinOp::Shl),
326+
hir::AssignOp::ShrAssign => Instruction::BinOp(BinOp::Shr),
327+
});
328+
329+
self.emit(Instruction::StoreVar(self.locals[name]));
330+
}
331+
306332
hir::Statement::DeclareAndAssign { name, value, .. } => {
307333
self.compile_expression(value);
308334
self.track_local(name);
@@ -1515,4 +1541,29 @@ mod tests {
15151541
)],
15161542
})
15171543
}
1544+
1545+
#[test]
1546+
fn basic_assign_add() -> anyhow::Result<()> {
1547+
assert_compiles(Program {
1548+
source: r#"
1549+
fn main() -> int {
1550+
let mut x = 1;
1551+
x += 2;
1552+
x
1553+
}
1554+
"#,
1555+
expected: vec![(
1556+
"main",
1557+
vec![
1558+
Instruction::LoadConst(0),
1559+
Instruction::LoadVar(1),
1560+
Instruction::LoadConst(1),
1561+
Instruction::BinOp(BinOp::Add),
1562+
Instruction::StoreVar(1),
1563+
Instruction::LoadVar(1),
1564+
Instruction::Return,
1565+
],
1566+
)],
1567+
})
1568+
}
15181569
}

engine/baml-compiler/src/hir/dump.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
use pretty::RcDoc;
44

55
use crate::hir::{
6-
Arrow, BinaryOperator, Block, Class, ClassConstructorField, Enum, EnumVariant, ExprFunction,
7-
Expression, Field, Hir, LlmFunction, Parameter, Statement, TypeArg, TypeM, TypeMeta,
8-
UnaryOperator,
6+
Arrow, AssignOp, BinaryOperator, Block, Class, ClassConstructorField, Enum, EnumVariant,
7+
ExprFunction, Expression, Field, Hir, LlmFunction, Parameter, Statement, TypeArg, TypeM,
8+
TypeMeta, UnaryOperator,
99
};
1010

1111
impl Hir {
@@ -117,12 +117,23 @@ impl Statement {
117117
.append(RcDoc::space())
118118
.append(RcDoc::text(name.clone()))
119119
.append(RcDoc::text(";")),
120-
Statement::Assign { name, value } => RcDoc::text(name.clone())
120+
Statement::Assign { name, value, .. } => RcDoc::text(name.clone())
121121
.append(RcDoc::space())
122122
.append(RcDoc::text("="))
123123
.append(RcDoc::space())
124124
.append(value.to_doc())
125125
.append(RcDoc::text(";")),
126+
Statement::AssignOp {
127+
name,
128+
value,
129+
assign_op,
130+
..
131+
} => RcDoc::text(name.clone())
132+
.append(RcDoc::space())
133+
.append(assign_op.to_doc())
134+
.append(RcDoc::space())
135+
.append(value.to_doc())
136+
.append(RcDoc::text(";")),
126137
Statement::DeclareAndAssign { name, value, .. } => RcDoc::text("var")
127138
.append(RcDoc::space())
128139
.append(RcDoc::text(name.clone()))
@@ -529,6 +540,23 @@ impl std::fmt::Display for UnaryOperator {
529540
}
530541
}
531542

543+
impl std::fmt::Display for AssignOp {
544+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545+
f.write_str(match self {
546+
AssignOp::AddAssign => "+=",
547+
AssignOp::SubAssign => "-=",
548+
AssignOp::MulAssign => "*=",
549+
AssignOp::DivAssign => "/=",
550+
AssignOp::ModAssign => "%=",
551+
AssignOp::BitXorAssign => "^=",
552+
AssignOp::BitAndAssign => "&=",
553+
AssignOp::BitOrAssign => "|=",
554+
AssignOp::ShlAssign => "<<=",
555+
AssignOp::ShrAssign => ">>=",
556+
})
557+
}
558+
}
559+
532560
impl BinaryOperator {
533561
pub fn to_doc(&self) -> RcDoc<'static, ()> {
534562
RcDoc::text(self.to_string())
@@ -540,3 +568,9 @@ impl UnaryOperator {
540568
RcDoc::text(self.to_string())
541569
}
542570
}
571+
572+
impl AssignOp {
573+
pub fn to_doc(&self) -> RcDoc<'static, ()> {
574+
RcDoc::text(self.to_string())
575+
}
576+
}

engine/baml-compiler/src/hir/lowering.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,34 @@ impl Block {
298298
expr,
299299
span,
300300
}) => {
301-
// Assignment statement (like let, without let) - check for if expressions in nested contexts
302-
// NOTE: Since we're not desugaring assignments, there will be no
303-
// lifted statements.
304-
let mut temp_counter = 0;
305-
let lifted_expr = Expression::from_ast(expr);
306-
307301
statements.push(Statement::Assign {
308302
name: identifier.to_string(),
309-
value: lifted_expr,
303+
value: Expression::from_ast(expr),
304+
span: span.clone(),
305+
});
306+
}
307+
ast::Stmt::AssignOp(ast::AssignOpStmt {
308+
identifier,
309+
assign_op,
310+
expr,
311+
span,
312+
}) => {
313+
statements.push(Statement::AssignOp {
314+
name: identifier.to_string(),
315+
assign_op: match assign_op {
316+
ast::AssignOp::AddAssign => hir::AssignOp::AddAssign,
317+
ast::AssignOp::SubAssign => hir::AssignOp::SubAssign,
318+
ast::AssignOp::MulAssign => hir::AssignOp::MulAssign,
319+
ast::AssignOp::DivAssign => hir::AssignOp::DivAssign,
320+
ast::AssignOp::ModAssign => hir::AssignOp::ModAssign,
321+
ast::AssignOp::BitXorAssign => hir::AssignOp::BitXorAssign,
322+
ast::AssignOp::BitAndAssign => hir::AssignOp::BitAndAssign,
323+
ast::AssignOp::BitOrAssign => hir::AssignOp::BitOrAssign,
324+
ast::AssignOp::ShlAssign => hir::AssignOp::ShlAssign,
325+
ast::AssignOp::ShrAssign => hir::AssignOp::ShrAssign,
326+
},
327+
value: Expression::from_ast(expr),
328+
span: span.clone(),
310329
});
311330
}
312331
ast::Stmt::Let(ast::LetStmt {

engine/baml-compiler/src/hir/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,13 @@ pub enum Statement {
334334
Assign {
335335
name: String,
336336
value: Expression,
337+
span: Span,
338+
},
339+
AssignOp {
340+
name: String,
341+
assign_op: AssignOp,
342+
value: Expression,
343+
span: Span,
337344
},
338345
/// Declare and assign a mutable reference in one statement.
339346
DeclareAndAssign {
@@ -368,6 +375,30 @@ pub enum Statement {
368375
},
369376
}
370377

378+
#[derive(Debug, Clone, Copy)]
379+
pub enum AssignOp {
380+
/// The `+=` operator (addition)
381+
AddAssign,
382+
/// The `-=` operator (subtraction)
383+
SubAssign,
384+
/// The `*=` operator (multiplication)
385+
MulAssign,
386+
/// The `/=` operator (division)
387+
DivAssign,
388+
/// The `%=` operator (modulus)
389+
ModAssign,
390+
/// The `^=` operator (bitwise xor)
391+
BitXorAssign,
392+
/// The `&=` operator (bitwise and)
393+
BitAndAssign,
394+
/// The `|=` operator (bitwise or)
395+
BitOrAssign,
396+
/// The `<<=` operator (shift left)
397+
ShlAssign,
398+
/// The `>>=` operator (shift right)
399+
ShrAssign,
400+
}
401+
371402
/// Expressions
372403
#[derive(Clone, Debug)]
373404
pub enum Expression {

engine/baml-compiler/src/thir/typecheck.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,9 @@ fn typecheck_statement(
344344
span: span.clone(),
345345
})
346346
}
347-
hir::Statement::Assign { name, value } => {
347+
// TODO: assign op needs more type checking?
348+
hir::Statement::Assign { name, value, .. }
349+
| hir::Statement::AssignOp { name, value, .. } => {
348350
let typed_value = typecheck_expression(value, context, diagnostics);
349351

350352
// validate/update type.

engine/baml-lib/ast/src/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub use identifier::{Identifier, RefIdentifier};
3636
pub use indentation_type::IndentationType;
3737
pub use internal_baml_diagnostics::Span;
3838
pub use newline_type::NewlineType;
39-
pub use stmt::{AssignStmt, ForLoopStmt, LetStmt, Stmt};
39+
pub use stmt::{AssignOp, AssignOpStmt, AssignStmt, ForLoopStmt, LetStmt, Stmt};
4040
pub use template_string::TemplateString;
4141
pub use top::Top;
4242
pub use traits::{WithAttributes, WithDocumentation, WithIdentifier, WithName, WithSpan};

engine/baml-lib/ast/src/ast/stmt.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,38 @@ pub struct AssignStmt {
1717
pub span: Span,
1818
}
1919

20+
#[derive(Debug, Clone)]
21+
pub struct AssignOpStmt {
22+
pub identifier: Identifier,
23+
pub assign_op: AssignOp,
24+
pub expr: Expression,
25+
pub span: Span,
26+
}
27+
28+
#[derive(Debug, Clone, Copy)]
29+
pub enum AssignOp {
30+
/// The `+=` operator (addition)
31+
AddAssign,
32+
/// The `-=` operator (subtraction)
33+
SubAssign,
34+
/// The `*=` operator (multiplication)
35+
MulAssign,
36+
/// The `/=` operator (division)
37+
DivAssign,
38+
/// The `%=` operator (modulus)
39+
ModAssign,
40+
/// The `^=` operator (bitwise xor)
41+
BitXorAssign,
42+
/// The `&=` operator (bitwise and)
43+
BitAndAssign,
44+
/// The `|=` operator (bitwise or)
45+
BitOrAssign,
46+
/// The `<<=` operator (shift left)
47+
ShlAssign,
48+
/// The `>>=` operator (shift right)
49+
ShrAssign,
50+
}
51+
2052
#[derive(Debug, Clone)]
2153
pub struct ForLoopStmt {
2254
pub identifier: Identifier,
@@ -33,6 +65,24 @@ pub enum Stmt {
3365
/// Expression with trailing semicolon.
3466
Expression(Expression),
3567
Assign(AssignStmt),
68+
AssignOp(AssignOpStmt),
69+
}
70+
71+
impl fmt::Display for AssignOp {
72+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73+
f.write_str(match self {
74+
AssignOp::AddAssign => "+=",
75+
AssignOp::SubAssign => "-=",
76+
AssignOp::MulAssign => "*=",
77+
AssignOp::DivAssign => "/=",
78+
AssignOp::ModAssign => "%=",
79+
AssignOp::BitAndAssign => "&=",
80+
AssignOp::BitOrAssign => "|=",
81+
AssignOp::BitXorAssign => "^=",
82+
AssignOp::ShlAssign => "<<=",
83+
AssignOp::ShrAssign => ">>=",
84+
})
85+
}
3686
}
3787

3888
impl fmt::Display for Stmt {
@@ -42,6 +92,9 @@ impl fmt::Display for Stmt {
4292
Stmt::ForLoop(stmt) => write!(f, "for {} in {}", stmt.identifier, stmt.iterator)?,
4393
Stmt::Expression(expr) => write!(f, "{expr}")?,
4494
Stmt::Assign(stmt) => write!(f, "{} = {}", stmt.identifier, stmt.expr)?,
95+
Stmt::AssignOp(stmt) => {
96+
write!(f, "{} {} {}", stmt.identifier, stmt.assign_op, stmt.expr)?
97+
}
4598
}
4699
Ok(())
47100
}
@@ -79,6 +132,7 @@ impl Stmt {
79132
Stmt::ForLoop(stmt) => &stmt.identifier,
80133
Stmt::Expression(expr) => panic!("expressions don't have identifiers"),
81134
Stmt::Assign(stmt) => &stmt.identifier,
135+
Stmt::AssignOp(stmt) => &stmt.identifier,
82136
}
83137
}
84138

@@ -88,6 +142,7 @@ impl Stmt {
88142
Stmt::ForLoop(stmt) => &stmt.span,
89143
Stmt::Expression(expr) => expr.span(),
90144
Stmt::Assign(stmt) => &stmt.span,
145+
Stmt::AssignOp(stmt) => &stmt.span,
91146
}
92147
}
93148

engine/baml-lib/ast/src/parser/datamodel.pest

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,26 @@ expr_block = { BLOCK_OPEN ~ NEWLINE? ~ (stmt | comment_block | empty_lines)* ~ e
323323
// With operator overloading (implicit function call) + side effects.
324324
//
325325
// Just allow any expr and fix the parser.
326-
stmt = { (((let_expr | assign_stmt | fn_app | generic_fn_app) ~ SEMICOLON) | for_loop | if_expression) ~ trailing_comment? ~ NEWLINE? }
326+
stmt = { (((let_expr | assign_op_stmt | assign_stmt | fn_app | generic_fn_app) ~ SEMICOLON) | for_loop | if_expression) ~ trailing_comment? ~ NEWLINE? }
327327

328328
// Let-binding statement.
329329
let_expr = { "let" ~ MUT_KEYWORD? ~ identifier ~ "=" ~ expression }
330330

331331
assign_stmt = { identifier ~ "=" ~ expression }
332+
assign_op_stmt = { identifier ~ assign_op ~ expression }
333+
334+
assign_op = _{ BIT_SHL_ASSIGN | BIT_SHR_ASSIGN | ADD_ASSIGN | SUB_ASSIGN | MUL_ASSIGN | DIV_ASSIGN | MOD_ASSIGN | BIT_AND_ASSIGN | BIT_OR_ASSIGN | BIT_XOR_ASSIGN }
335+
336+
ADD_ASSIGN = { "+=" }
337+
SUB_ASSIGN = { "-=" }
338+
MUL_ASSIGN = { "*=" }
339+
DIV_ASSIGN = { "/=" }
340+
MOD_ASSIGN = { "%=" }
341+
BIT_AND_ASSIGN = { "&=" }
342+
BIT_OR_ASSIGN = { "|=" }
343+
BIT_XOR_ASSIGN = { "^=" }
344+
BIT_SHL_ASSIGN = { "<<=" }
345+
BIT_SHR_ASSIGN = { ">>=" }
332346

333347
fn_args = { expression? ~ ("," ~ expression)* }
334348

0 commit comments

Comments
 (0)