Skip to content

Commit

Permalink
feat: loop statement
Browse files Browse the repository at this point in the history
  • Loading branch information
fcoury committed Jul 5, 2024
1 parent 3d48e06 commit 16a9921
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ impl Interpreter {
}
}
},
Stmt::Loop(body, _span) => loop {
for stmt in body {
let (_, control_flow) = self.execute_stmt(stmt)?;
match control_flow {
ControlFlow::Break => return Ok((Value::Unit, ControlFlow::Normal)),
ControlFlow::Continue => break,
ControlFlow::Normal => {}
}
}
},
Stmt::Break(_) => return Ok((Value::Unit, ControlFlow::Break)),
Stmt::Continue(_) => return Ok((Value::Unit, ControlFlow::Continue)),
}
Expand Down Expand Up @@ -1151,4 +1161,23 @@ mod tests {
Err(e) => panic!("{}", e.pretty_print(code)),
}
}

#[test]
fn test_loop() {
let code = r#"
let x = 0;
loop {
x += 1;
if x == 5 {
break;
}
}
x
"#;

match run_code(code, None) {
Ok(val) => assert_eq!(val, Value::Int(5)),
Err(e) => panic!("{}", e.pretty_print(code)),
}
}
}
28 changes: 28 additions & 0 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub enum TokenKind {
If,
Else,
Match,
Loop,
While,
For,
In,
Expand Down Expand Up @@ -314,6 +315,7 @@ impl Lexer {
"if" => TokenKind::If,
"else" => TokenKind::Else,
"match" => TokenKind::Match,
"loop" => TokenKind::Loop,
"while" => TokenKind::While,
"for" => TokenKind::For,
"in" => TokenKind::In,
Expand Down Expand Up @@ -1160,4 +1162,30 @@ mod tests {
assert_eq!(kind, expected);
}
}

#[test]
fn test_lex_loop() {
let code = r#"
loop {
x += 1;
}
"#;

let mut lexer = Lexer::new(code);
let expected_tokens = vec![
TokenKind::Loop,
TokenKind::LBrace,
TokenKind::Identifier("x".to_string()),
TokenKind::PlusEquals,
TokenKind::Int(1),
TokenKind::Semicolon,
TokenKind::RBrace,
TokenKind::Eof,
];

for expected in expected_tokens {
let kind = lexer.next_token().kind;
assert_eq!(kind, expected);
}
}
}
45 changes: 45 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ pub enum Stmt {
Enum(String, Vec<(String, String)>, Span),
ForLoop(String, Expr, Vec<Stmt>, Span),
While(Expr, Vec<Stmt>, Span),
Loop(Vec<Stmt>, Span),
Break(Span),
Continue(Span),
}
Expand Down Expand Up @@ -338,12 +339,33 @@ impl Parser {
TokenKind::Match => self.parse_match_statement(),
TokenKind::For => self.parse_for_loop(),
TokenKind::While => self.parse_while_statement(),
TokenKind::Loop => self.parse_loop_statement(),
TokenKind::Break => self.parse_break(),
TokenKind::Continue => self.parse_continue(),
_ => self.parse_expression_statement(),
}
}

fn parse_loop_statement(&mut self) -> Result<Stmt> {
let start_span = self.current_token().span.start;
self.advance(); // Consume 'loop'

if self.current_token().kind != TokenKind::LBrace {
return Err(Error::new_parse(
"Expected '{' after 'loop'".to_string(),
self.current_token().span,
));
}

self.advance(); // Consume '{'
let body = self.parse_block()?;

let end_span = self.current_token().span.end;
self.advance(); // Consume '}'

Ok(Stmt::Loop(body, Span::new(start_span, end_span)))
}

fn parse_break(&mut self) -> Result<Stmt> {
let span = self.current_token().span;
self.advance(); // Consume 'break'
Expand Down Expand Up @@ -2195,4 +2217,27 @@ mod tests {
)
);
}

#[test]
fn test_parse_loop() {
let code = r#"
loop {
print("Hello");
}
"#;

let ast = parse(code);

assert_eq!(
ast[0],
Stmt::Loop(
vec![Stmt::Expression(Expr::FunctionCall(
"print".to_string(),
vec![Expr::String("Hello".to_string(), Span::new(29, 36))],
Span::new(23, 37),
))],
Span::new(13, 43),
)
);
}
}
9 changes: 9 additions & 0 deletions src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ impl SemanticAnalyzer {

Ok(())
}
Stmt::Loop(body, _span) => {
self.loop_depth += 1;
for stmt in body {
self.analyze_stmt(stmt)?;
}
self.loop_depth -= 1;

Ok(())
}
Stmt::Break(span) => {
if self.loop_depth == 0 {
return Err(Error::new_semantic(
Expand Down
10 changes: 10 additions & 0 deletions tests/scripts/loop.hk
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ for i in 1..=10 {
}
println(i)
}

let i = 0;
loop {
println(i);
i += 3;

if i > 20 {
break;
}
}

0 comments on commit 16a9921

Please sign in to comment.