feat: add support for let statements
This commit is contained in:
@@ -101,4 +101,7 @@ run_harness_test "tests/booleans.src" "tests/booleans.c" 0
|
|||||||
# Test if and else statements.
|
# Test if and else statements.
|
||||||
run_harness_test "tests/if-else.src" "tests/if-else.c" 0
|
run_harness_test "tests/if-else.src" "tests/if-else.c" 0
|
||||||
|
|
||||||
|
# Test variable declarations and scoping.
|
||||||
|
run_test "tests/let_stmt.src" 30
|
||||||
|
|
||||||
echo "All end-to-end tests passed!"
|
echo "All end-to-end tests passed!"
|
||||||
@@ -101,6 +101,12 @@ pub enum StmtKind<P: Phase = Untyped> {
|
|||||||
Return {
|
Return {
|
||||||
value: Option<Expr<P>>,
|
value: Option<Expr<P>>,
|
||||||
},
|
},
|
||||||
|
Let {
|
||||||
|
name: String,
|
||||||
|
name_span: Span,
|
||||||
|
ty: P::ReturnType,
|
||||||
|
initializer: Option<Expr<P>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ impl<'src> Lexer<'src> {
|
|||||||
"if" => TokenKind::If,
|
"if" => TokenKind::If,
|
||||||
"else" => TokenKind::Else,
|
"else" => TokenKind::Else,
|
||||||
"return" => TokenKind::Return,
|
"return" => TokenKind::Return,
|
||||||
|
"let" => TokenKind::Let,
|
||||||
|
|
||||||
"i8" => TokenKind::I8,
|
"i8" => TokenKind::I8,
|
||||||
"i16" => TokenKind::I16,
|
"i16" => TokenKind::I16,
|
||||||
@@ -205,7 +206,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn identifiers() {
|
fn identifiers() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tokenize("HELLO _hello _0@ fn if else return"),
|
tokenize("HELLO _hello _0@ fn if else return let"),
|
||||||
vec![
|
vec![
|
||||||
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
||||||
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
||||||
@@ -215,6 +216,7 @@ mod test {
|
|||||||
Token::new(TokenKind::If, "if", Span::new(20, 22)),
|
Token::new(TokenKind::If, "if", Span::new(20, 22)),
|
||||||
Token::new(TokenKind::Else, "else", Span::new(23, 27)),
|
Token::new(TokenKind::Else, "else", Span::new(23, 27)),
|
||||||
Token::new(TokenKind::Return, "return", Span::new(28, 34)),
|
Token::new(TokenKind::Return, "return", Span::new(28, 34)),
|
||||||
|
Token::new(TokenKind::Let, "let", Span::new(35, 38)),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-1
@@ -285,7 +285,9 @@ impl<'src> Parser<'src> {
|
|||||||
/// ```ebnf
|
/// ```ebnf
|
||||||
/// stmt = compound_stmt
|
/// stmt = compound_stmt
|
||||||
/// | if_stmt
|
/// | if_stmt
|
||||||
/// | return_stmt ;
|
/// | return_stmt
|
||||||
|
/// | let_stmt
|
||||||
|
/// ;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
|
pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
let peek_token = self.peek_no_eof()?;
|
let peek_token = self.peek_no_eof()?;
|
||||||
@@ -294,6 +296,7 @@ impl<'src> Parser<'src> {
|
|||||||
TokenKind::LBrace => self.parse_compound_stmt(),
|
TokenKind::LBrace => self.parse_compound_stmt(),
|
||||||
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(),
|
||||||
|
|
||||||
_ => Err(ParseError::new(
|
_ => Err(ParseError::new(
|
||||||
format!("expected a statement but found {} instead", peek_token.kind),
|
format!("expected a statement but found {} instead", peek_token.kind),
|
||||||
@@ -398,6 +401,47 @@ impl<'src> Parser<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a let statement.
|
||||||
|
///
|
||||||
|
/// ```ebnf
|
||||||
|
/// let_stmt = "let" IDENTIFIER [ ":" type ] [ "=" expr ] ";" ;
|
||||||
|
/// ```
|
||||||
|
fn parse_let_stmt(&mut self) -> ParseResult<Stmt> {
|
||||||
|
let let_token = self.expect(TokenKind::Let)?;
|
||||||
|
|
||||||
|
let (name, name_span) = {
|
||||||
|
let ident_token = self.expect(TokenKind::Identifier)?;
|
||||||
|
(ident_token.text.to_string(), ident_token.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ty = if self.is_peek(TokenKind::Colon) {
|
||||||
|
self.advance();
|
||||||
|
Some(self.parse_type()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let initializer = if self.is_peek(TokenKind::Assign) {
|
||||||
|
self.advance();
|
||||||
|
Some(self.parse_expr()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||||
|
let span = let_token.span.join(semi_token.span);
|
||||||
|
|
||||||
|
Ok(Stmt {
|
||||||
|
kind: StmtKind::Let {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
ty,
|
||||||
|
initializer,
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ====== Pratt Parsing Implementation ======
|
// ====== Pratt Parsing Implementation ======
|
||||||
|
|
||||||
/// Parses an expression.
|
/// Parses an expression.
|
||||||
@@ -795,6 +839,42 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_stmt() {
|
||||||
|
assert_eq!(
|
||||||
|
parse("let a: i32 = 5;", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::Let {
|
||||||
|
name: "a".to_string(),
|
||||||
|
name_span: Span::new(4, 5),
|
||||||
|
ty: Some(Type {
|
||||||
|
kind: TypeKind::I32,
|
||||||
|
span: Span::new(7, 10),
|
||||||
|
}),
|
||||||
|
initializer: Some(Expr {
|
||||||
|
kind: ExprKind::Integer { value: 5 },
|
||||||
|
ty: (),
|
||||||
|
span: Span::new(13, 14),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
span: Span::new(0, 15)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parse("let a;", Parser::parse_stmt),
|
||||||
|
Success(Stmt {
|
||||||
|
kind: StmtKind::Let {
|
||||||
|
name: "a".to_string(),
|
||||||
|
name_span: Span::new(4, 5),
|
||||||
|
ty: None,
|
||||||
|
initializer: None,
|
||||||
|
},
|
||||||
|
span: Span::new(0, 6)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_decl() {
|
fn function_decl() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -348,6 +348,48 @@ impl Sema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StmtKind::Let {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
ty: type_annotation,
|
||||||
|
initializer,
|
||||||
|
} => {
|
||||||
|
let explicit_ty = type_annotation.as_ref().map(|t| Ty::from(&t.kind));
|
||||||
|
|
||||||
|
let (inferred_ty, typed_initializer) = match initializer {
|
||||||
|
Some(expr) => {
|
||||||
|
let typed_expr = self.analyze_expr(expr);
|
||||||
|
if let Some(ref ext_ty) = explicit_ty {
|
||||||
|
if let Err(err) = self.unify(&typed_expr.ty, ext_ty) {
|
||||||
|
self.errors.push(SemanticError::new(err, expr.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(typed_expr.ty.clone(), Some(typed_expr))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if let Some(ref ext_ty) = explicit_ty {
|
||||||
|
(ext_ty.clone(), None)
|
||||||
|
} else {
|
||||||
|
self.errors
|
||||||
|
.push(SemanticError::new("type annotation needed", *name_span));
|
||||||
|
(self.new_var(), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let final_ty = explicit_ty.unwrap_or(inferred_ty);
|
||||||
|
self.bind(name, final_ty.clone());
|
||||||
|
|
||||||
|
TypedStmt {
|
||||||
|
kind: TypedStmtKind::Let {
|
||||||
|
name: name.clone(),
|
||||||
|
name_span: *name_span,
|
||||||
|
ty: final_ty,
|
||||||
|
initializer: typed_initializer,
|
||||||
|
},
|
||||||
|
span: stmt.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +574,18 @@ impl Sema {
|
|||||||
TypedStmtKind::Return { value } => TypedStmtKind::Return {
|
TypedStmtKind::Return { value } => TypedStmtKind::Return {
|
||||||
value: value.map(|e| self.apply_subst_expr(e)),
|
value: value.map(|e| self.apply_subst_expr(e)),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
TypedStmtKind::Let {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
ty,
|
||||||
|
initializer,
|
||||||
|
} => TypedStmtKind::Let {
|
||||||
|
name,
|
||||||
|
name_span,
|
||||||
|
ty: self.apply_subst(&ty),
|
||||||
|
initializer: initializer.map(|e| self.apply_subst_expr(e)),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
TypedStmt { kind, span }
|
TypedStmt { kind, span }
|
||||||
@@ -797,4 +851,16 @@ mod test {
|
|||||||
let src = "fn test() { if 12 {} }";
|
let src = "fn test() { if 12 {} }";
|
||||||
assert!(analyze(src).is_err());
|
assert!(analyze(src).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn valid_let() {
|
||||||
|
let src = "fn test() { let a = 5; let b: i32 = a; }";
|
||||||
|
assert!(analyze(src).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_missing_type() {
|
||||||
|
let src = "fn test() { let a; }";
|
||||||
|
assert!(analyze(src).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub enum TokenKind {
|
|||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
Return,
|
Return,
|
||||||
|
Let,
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
I8,
|
I8,
|
||||||
@@ -115,6 +116,7 @@ impl Display for TokenKind {
|
|||||||
TokenKind::If => "`if`",
|
TokenKind::If => "`if`",
|
||||||
TokenKind::Else => "`else`",
|
TokenKind::Else => "`else`",
|
||||||
TokenKind::Return => "`return`",
|
TokenKind::Return => "`return`",
|
||||||
|
TokenKind::Let => "`let`",
|
||||||
TokenKind::I8 => "`i8`",
|
TokenKind::I8 => "`i8`",
|
||||||
TokenKind::I16 => "`i16`",
|
TokenKind::I16 => "`i16`",
|
||||||
TokenKind::I32 => "`i32`",
|
TokenKind::I32 => "`i32`",
|
||||||
|
|||||||
+40
-8
@@ -83,8 +83,8 @@ struct FuncBuilder {
|
|||||||
/// Counter for generating unique `BlockId`s.
|
/// Counter for generating unique `BlockId`s.
|
||||||
next_block_id: usize,
|
next_block_id: usize,
|
||||||
|
|
||||||
/// Mapping from user-defined variable names to their corresponding `LocalId`.
|
/// Scoped mapping from user-defined variable names to their corresponding `LocalId`.
|
||||||
vars: HashMap<String, LocalId>,
|
scopes: Vec<HashMap<String, LocalId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncBuilder {
|
impl FuncBuilder {
|
||||||
@@ -99,10 +99,18 @@ impl FuncBuilder {
|
|||||||
current_block: None,
|
current_block: None,
|
||||||
current_statements: Vec::new(),
|
current_statements: Vec::new(),
|
||||||
next_block_id: 0,
|
next_block_id: 0,
|
||||||
vars: HashMap::new(),
|
scopes: vec![HashMap::new()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enter_scope(&mut self) {
|
||||||
|
self.scopes.push(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_scope(&mut self) {
|
||||||
|
self.scopes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers a new user-defined local variable and returns its `LocalId`.
|
/// Registers a new user-defined local variable and returns its `LocalId`.
|
||||||
fn new_local(&mut self, name: String, ty: Ty) -> LocalId {
|
fn new_local(&mut self, name: String, ty: Ty) -> LocalId {
|
||||||
let id = LocalId(self.locals.len());
|
let id = LocalId(self.locals.len());
|
||||||
@@ -112,7 +120,7 @@ impl FuncBuilder {
|
|||||||
mutable: false,
|
mutable: false,
|
||||||
name: Some(name.clone()),
|
name: Some(name.clone()),
|
||||||
});
|
});
|
||||||
self.vars.insert(name, id);
|
self.scopes.last_mut().unwrap().insert(name, id);
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +136,15 @@ impl FuncBuilder {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lookup(&self, name: &str) -> LocalId {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if let Some(id) = scope.get(name) {
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("undeclared variable `{}` in MIR lowering", name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Allocates a new, empty basic block and returns its `BlockId`.
|
/// Allocates a new, empty basic block and returns its `BlockId`.
|
||||||
fn new_block(&mut self) -> BlockId {
|
fn new_block(&mut self) -> BlockId {
|
||||||
let id = BlockId(self.next_block_id);
|
let id = BlockId(self.next_block_id);
|
||||||
@@ -180,9 +197,11 @@ impl FuncBuilder {
|
|||||||
fn lower_stmt(&mut self, stmt: &TypedStmt) {
|
fn lower_stmt(&mut self, stmt: &TypedStmt) {
|
||||||
match &stmt.kind {
|
match &stmt.kind {
|
||||||
TypedStmtKind::Compound { inner } => {
|
TypedStmtKind::Compound { inner } => {
|
||||||
|
self.enter_scope();
|
||||||
for s in inner {
|
for s in inner {
|
||||||
self.lower_stmt(s);
|
self.lower_stmt(s);
|
||||||
}
|
}
|
||||||
|
self.leave_scope();
|
||||||
}
|
}
|
||||||
TypedStmtKind::If {
|
TypedStmtKind::If {
|
||||||
condition,
|
condition,
|
||||||
@@ -237,6 +256,22 @@ impl FuncBuilder {
|
|||||||
span: stmt.span,
|
span: stmt.span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
TypedStmtKind::Let {
|
||||||
|
name,
|
||||||
|
name_span: _,
|
||||||
|
ty,
|
||||||
|
initializer,
|
||||||
|
} => {
|
||||||
|
let local_id = self.new_local(name.clone(), ty.clone());
|
||||||
|
|
||||||
|
if let Some(init_expr) = initializer {
|
||||||
|
let val_op = self.lower_expr(init_expr);
|
||||||
|
self.emit_stmt(Statement {
|
||||||
|
kind: StatementKind::Assign(local_id, Rvalue::Use(val_op)),
|
||||||
|
span: stmt.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,10 +279,7 @@ impl FuncBuilder {
|
|||||||
fn lower_expr(&mut self, expr: &TypedExpr) -> Operand {
|
fn lower_expr(&mut self, expr: &TypedExpr) -> Operand {
|
||||||
match &expr.kind {
|
match &expr.kind {
|
||||||
TypedExprKind::Identifier { name } => {
|
TypedExprKind::Identifier { name } => {
|
||||||
let local = *self
|
let local = self.lookup(name);
|
||||||
.vars
|
|
||||||
.get(name)
|
|
||||||
.expect("undeclared variable in MIR lowering");
|
|
||||||
Operand::Copy(local)
|
Operand::Copy(local)
|
||||||
}
|
}
|
||||||
TypedExprKind::Integer { value } => {
|
TypedExprKind::Integer { value } => {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fn main() -> i32 {
|
||||||
|
let a = 10;
|
||||||
|
let b: i32 = 20;
|
||||||
|
let c = a + b;
|
||||||
|
{
|
||||||
|
// Shadow 'a' and 'c' in a new scope
|
||||||
|
let a = 5;
|
||||||
|
let c = a + b;
|
||||||
|
}
|
||||||
|
return c; // Should return 30, not 25, because the inner 'c' is dropped
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user