feat: add expression statements and assign expressions
This commit is contained in:
@@ -104,4 +104,7 @@ run_harness_test "tests/if-else.src" "tests/if-else.c" 0
|
|||||||
# Test variable declarations and scoping.
|
# Test variable declarations and scoping.
|
||||||
run_test "tests/let_stmt.src" 30
|
run_test "tests/let_stmt.src" 30
|
||||||
|
|
||||||
|
# Test assignments and multi-assignments
|
||||||
|
run_test "tests/assign.src" 42
|
||||||
|
|
||||||
echo "All end-to-end tests passed!"
|
echo "All end-to-end tests passed!"
|
||||||
@@ -107,6 +107,9 @@ pub enum StmtKind<P: Phase = Untyped> {
|
|||||||
ty: P::ReturnType,
|
ty: P::ReturnType,
|
||||||
initializer: Option<Expr<P>>,
|
initializer: Option<Expr<P>>,
|
||||||
},
|
},
|
||||||
|
Expression {
|
||||||
|
expr: Expr<P>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -136,6 +139,10 @@ pub enum ExprKind<P: Phase = Untyped> {
|
|||||||
lhs: Box<Expr<P>>,
|
lhs: Box<Expr<P>>,
|
||||||
rhs: Box<Expr<P>>,
|
rhs: Box<Expr<P>>,
|
||||||
},
|
},
|
||||||
|
Assign {
|
||||||
|
lval: Box<Expr<P>>,
|
||||||
|
rval: Box<Expr<P>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
+83
-5
@@ -297,11 +297,7 @@ impl<'src> Parser<'src> {
|
|||||||
TokenKind::If => self.parse_if_stmt(),
|
TokenKind::If => self.parse_if_stmt(),
|
||||||
TokenKind::Return => self.parse_return_stmt(),
|
TokenKind::Return => self.parse_return_stmt(),
|
||||||
TokenKind::Let => self.parse_let_stmt(),
|
TokenKind::Let => self.parse_let_stmt(),
|
||||||
|
_ => self.parse_expr_stmt(),
|
||||||
_ => Err(ParseError::new(
|
|
||||||
format!("expected a statement but found {} instead", peek_token.kind),
|
|
||||||
peek_token.span,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +374,22 @@ impl<'src> Parser<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses an expression statement.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// expr_stmt = expr ";" ;
|
||||||
|
/// ```
|
||||||
|
fn parse_expr_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||||
|
let span = expr.span.join(semi_token.span);
|
||||||
|
|
||||||
|
Ok(Stmt {
|
||||||
|
kind: StmtKind::Expression { expr },
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a return statement.
|
/// Parses a return statement.
|
||||||
///
|
///
|
||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
@@ -456,6 +468,29 @@ impl<'src> Parser<'src> {
|
|||||||
loop {
|
loop {
|
||||||
let peek_token = self.peek_no_eof()?;
|
let peek_token = self.peek_no_eof()?;
|
||||||
|
|
||||||
|
if peek_token.kind == TokenKind::Assign {
|
||||||
|
let left_bp = 2;
|
||||||
|
let right_bp = 1; // Right-associative mapping
|
||||||
|
|
||||||
|
if left_bp < min_bp {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.advance(); // consume '='
|
||||||
|
let rhs = self.parse_expr_bp(right_bp)?;
|
||||||
|
let span = lhs.span.join(rhs.span);
|
||||||
|
|
||||||
|
lhs = Expr {
|
||||||
|
kind: ExprKind::Assign {
|
||||||
|
lval: Box::new(lhs),
|
||||||
|
rval: Box::new(rhs),
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span,
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
||||||
break; // Not an infix operator
|
break; // Not an infix operator
|
||||||
};
|
};
|
||||||
@@ -875,6 +910,49 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assign_expr_stmt() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("a = b = 5;", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::Expression {
|
||||||
|
expr: Expr {
|
||||||
|
kind: ExprKind::Assign {
|
||||||
|
lval: Box::new(Expr {
|
||||||
|
kind: ExprKind::Identifier {
|
||||||
|
name: "a".to_string()
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(0, 1)
|
||||||
|
}),
|
||||||
|
rval: Box::new(Expr {
|
||||||
|
kind: ExprKind::Assign {
|
||||||
|
lval: Box::new(Expr {
|
||||||
|
kind: ExprKind::Identifier {
|
||||||
|
name: "b".to_string()
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(4, 5)
|
||||||
|
}),
|
||||||
|
rval: Box::new(Expr {
|
||||||
|
kind: ExprKind::Integer { value: 5 },
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(8, 9)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(4, 9)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(0, 9)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
span: Span::new(0, 10)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_decl() {
|
fn function_decl() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -390,6 +390,14 @@ impl Sema {
|
|||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StmtKind::Expression { expr } => {
|
||||||
|
let typed_expr = self.analyze_expr(expr);
|
||||||
|
|
||||||
|
TypedStmt {
|
||||||
|
kind: TypedStmtKind::Expression { expr: typed_expr },
|
||||||
|
span: stmt.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +526,31 @@ impl Sema {
|
|||||||
span: expr.span,
|
span: expr.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ExprKind::Assign { lval, rval } => {
|
||||||
|
let typed_rval = self.analyze_expr(rval);
|
||||||
|
let typed_lval = self.analyze_expr(lval);
|
||||||
|
|
||||||
|
match &typed_lval.kind {
|
||||||
|
TypedExprKind::Identifier { .. } => {}
|
||||||
|
_ => self.errors.push(SemanticError::new(
|
||||||
|
"invalid left-hand side of assignment",
|
||||||
|
lval.span,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = self.unify(&typed_lval.ty, &typed_rval.ty) {
|
||||||
|
self.errors.push(SemanticError::new(e, expr.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedExpr {
|
||||||
|
ty: typed_rval.ty.clone(),
|
||||||
|
kind: TypedExprKind::Assign {
|
||||||
|
lval: Box::new(typed_lval),
|
||||||
|
rval: Box::new(typed_rval),
|
||||||
|
},
|
||||||
|
span: expr.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,6 +619,9 @@ impl Sema {
|
|||||||
ty: self.apply_subst(&ty),
|
ty: self.apply_subst(&ty),
|
||||||
initializer: initializer.map(|e| self.apply_subst_expr(e)),
|
initializer: initializer.map(|e| self.apply_subst_expr(e)),
|
||||||
},
|
},
|
||||||
|
TypedStmtKind::Expression { expr } => TypedStmtKind::Expression {
|
||||||
|
expr: self.apply_subst_expr(expr),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypedStmt { kind, span }
|
TypedStmt { kind, span }
|
||||||
@@ -610,6 +646,10 @@ impl Sema {
|
|||||||
lhs: Box::new(self.apply_subst_expr(*lhs)),
|
lhs: Box::new(self.apply_subst_expr(*lhs)),
|
||||||
rhs: Box::new(self.apply_subst_expr(*rhs)),
|
rhs: Box::new(self.apply_subst_expr(*rhs)),
|
||||||
},
|
},
|
||||||
|
TypedExprKind::Assign { lval, rval } => TypedExprKind::Assign {
|
||||||
|
lval: Box::new(self.apply_subst_expr(*lval)),
|
||||||
|
rval: Box::new(self.apply_subst_expr(*rval)),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypedExpr { kind, ty, span }
|
TypedExpr { kind, ty, span }
|
||||||
@@ -863,4 +903,34 @@ mod test {
|
|||||||
let src = "fn test() { let a; }";
|
let src = "fn test() { let a; }";
|
||||||
assert!(analyze(src).is_err());
|
assert!(analyze(src).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_expression_stmt() {
|
||||||
|
let src = "fn test() { 5 + 5; }";
|
||||||
|
assert!(analyze(src).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_assignment() {
|
||||||
|
let src = "fn test() { let a = 5; a = 10; }";
|
||||||
|
assert!(analyze(src).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_assignment_type() {
|
||||||
|
let src = "fn test() { let a: i32 = 5; a = true; }";
|
||||||
|
let errors = analyze(src).unwrap_err();
|
||||||
|
assert!(errors.iter().any(|e| e.message.contains("type mismatch")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_lvalue() {
|
||||||
|
let src = "fn test() { 5 = 10; }";
|
||||||
|
let errors = analyze(src).unwrap_err();
|
||||||
|
assert!(
|
||||||
|
errors
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.message.contains("invalid left-hand side of assignment"))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,6 +272,9 @@ impl FuncBuilder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TypedStmtKind::Expression { expr } => {
|
||||||
|
self.lower_expr(expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +312,21 @@ impl FuncBuilder {
|
|||||||
|
|
||||||
Operand::Copy(temp)
|
Operand::Copy(temp)
|
||||||
}
|
}
|
||||||
|
TypedExprKind::Assign { lval, rval } => {
|
||||||
|
let rval_op = self.lower_expr(rval);
|
||||||
|
|
||||||
|
let local_id = match &lval.kind {
|
||||||
|
TypedExprKind::Identifier { name } => self.lookup(name),
|
||||||
|
_ => panic!("invalid lval in MIR lowering"),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.emit_stmt(Statement {
|
||||||
|
kind: StatementKind::Assign(local_id, Rvalue::Use(rval_op.clone())),
|
||||||
|
span: expr.span,
|
||||||
|
});
|
||||||
|
|
||||||
|
rval_op
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ fn optimize_function(func: &mut MirFunction) {
|
|||||||
// Replace the complex instruction with a simple constant use
|
// Replace the complex instruction with a simple constant use
|
||||||
*rvalue = Rvalue::Use(Operand::Constant(constant.clone()));
|
*rvalue = Rvalue::Use(Operand::Constant(constant.clone()));
|
||||||
known_constants.insert(*local, constant);
|
known_constants.insert(*local, constant);
|
||||||
|
} else {
|
||||||
|
// Reassigned to a non-computable value; remove older cached inferences
|
||||||
|
known_constants.remove(local);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fn main() -> i32 {
|
||||||
|
let a = 0;
|
||||||
|
let b = 0;
|
||||||
|
a = b = 42;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user