From 62ff55df7a0d6661fe4ad23e80e53113ef1bf83b Mon Sep 17 00:00:00 2001 From: me Date: Sat, 20 Dec 2025 23:30:08 +0200 Subject: [PATCH] loops --- src/ast/types.rs | 10 +- src/interpret/interpret.rs | 23 +++- src/lib.rs | 22 +++ src/parser/parser.rs | 42 ++++++ src/parser/scanner.rs | 2 + ...parser__parser__tests__let_loop_count.snap | 128 ++++++++++++++++++ src/parser/types.rs | 2 + .../ayin__tests__loop_counter_9.snap | 11 ++ 8 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 src/parser/snapshots/ayin__parser__parser__tests__let_loop_count.snap create mode 100644 src/snapshots/ayin__tests__loop_counter_9.snap diff --git a/src/ast/types.rs b/src/ast/types.rs index e4fb0f9..c1c809b 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -30,13 +30,6 @@ pub enum Expr { }, Record(BTreeMap), Vector(Vec), - /* - Loop { - body: Box, - }, - Continue, - Break, - */ } #[derive(PartialEq, PartialOrd, Debug, Clone)] @@ -80,6 +73,8 @@ pub enum Statement { Expr(Expr), Let(Definition), Return(Option), + Loop(Box), + Break, } #[derive(PartialEq, PartialOrd, Debug, Clone)] @@ -103,6 +98,7 @@ pub enum Value { Closure { env: EnvName, expr: Box }, PrimitiveFunc(Name), Return(Box), + Break, } /// A mutable variable. diff --git a/src/interpret/interpret.rs b/src/interpret/interpret.rs index fa34547..5d8d221 100644 --- a/src/interpret/interpret.rs +++ b/src/interpret/interpret.rs @@ -48,6 +48,7 @@ pub fn run( enum StatementResult { Result(ast::Value), Return(ast::Value), + Break, } fn eval_statement( @@ -56,7 +57,11 @@ fn eval_statement( statement: &ast::Statement, ) -> Result { match statement { - ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr).map(StatementResult::Result), + ast::Statement::Expr(expr) => match eval_expr(expr_env, state, expr)? { + ast::Value::Break => Ok(StatementResult::Break), + ast::Value::Return(v) => Ok(StatementResult::Return(*v)), + v => Ok(StatementResult::Result(v)), + }, ast::Statement::Let(ast::Definition { mutable, name, @@ -74,6 +79,18 @@ fn eval_statement( Some(expr) => eval_expr(expr_env, state, expr).map(StatementResult::Return), None => eval_expr(expr_env, state, &ast::UNIT).map(StatementResult::Return), }, + ast::Statement::Break => Ok(StatementResult::Break), + ast::Statement::Loop(expr) => { + loop { + match eval_expr(expr_env, state, expr)? { + ast::Value::Break => { + break; + } + _ => {} + } + } + Ok(StatementResult::Result(ast::UNIT_VALUE)) + } } } @@ -106,6 +123,10 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result { + result = ast::Value::Break; + break; + } StatementResult::Result(ast::Value::Return(r)) => { result = ast::Value::Return(r); break; diff --git a/src/lib.rs b/src/lib.rs index 28f1915..5036920 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,28 @@ let main = fn() { insta::assert_debug_snapshot!(result); } + #[test] + fn loop_counter_9() { + let program = " +let counter = fn(a,b) { + let mut count = 0; + loop { + if (a + count) >= b { + break; + }; + count = count + 1; + }; + count +} + +let main = fn() { + counter(1,10) +} +"; + let result = run_main(program); + insta::assert_debug_snapshot!(result); + } + #[test] fn direction() { let program = " diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 15b4f9c..f5e3266 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -270,6 +270,30 @@ fn parse_statement(tokens: &mut Tokens) -> ParseResult { Ok(None) } }, + |tokens| { + if let Some(_) = tokens.next_if(&Token::Break) { + Ok(Some(ast::Statement::Break)) + } else { + Ok(None) + } + }, + |t| { + if let Some(_) = t.next_if(&Token::Loop) { + if let Some(block) = t + .between(&Token::OpenCurly, &Token::CloseCurly, parse_block)? + .map(ast::Expr::Block) + { + Ok(Some(ast::Statement::Loop(Box::new(block)))) + } else { + Err(Error::UnexpectedTokenForParser( + "loop expression".into(), + t.next(), + )) + } + } else { + Ok(None) + } + }, ]) } @@ -515,6 +539,24 @@ mod tests { insta::assert_debug_snapshot!(result); } #[test] + fn let_loop_count() { + let program = " +let counter = fn(a,b) { + let mut count = 0; + loop { + if (a + count) >= b { + break; + }; + count = count + 1; + }; + count +} +" + .to_string(); + let result = parse(&mut scan(program), parse_program); + insta::assert_debug_snapshot!(result); + } + #[test] fn full_program() { let program = " let setup = fn() { diff --git a/src/parser/scanner.rs b/src/parser/scanner.rs index 7a7ee52..62a632f 100644 --- a/src/parser/scanner.rs +++ b/src/parser/scanner.rs @@ -306,6 +306,8 @@ pub fn scan(source: String) -> Tokens { "if" => Token::If, "else" => Token::Else, "return" => Token::Return, + "loop" => Token::Loop, + "break" => Token::Break, "true" => Token::True, "false" => Token::False, _ => Token::Identifier(str), diff --git a/src/parser/snapshots/ayin__parser__parser__tests__let_loop_count.snap b/src/parser/snapshots/ayin__parser__parser__tests__let_loop_count.snap new file mode 100644 index 0000000..45abc09 --- /dev/null +++ b/src/parser/snapshots/ayin__parser__parser__tests__let_loop_count.snap @@ -0,0 +1,128 @@ +--- +source: src/parser/parser.rs +expression: result +--- +Ok( + Program( + [ + Definition { + mutable: false, + name: Name( + "counter", + ), + expr: Func( + Fn { + args: [ + Arg { + name: Name( + "a", + ), + }, + Arg { + name: Name( + "b", + ), + }, + ], + body: Block( + [ + Let( + Definition { + mutable: true, + name: Name( + "count", + ), + expr: Value( + Int( + 0, + ), + ), + }, + ), + Loop( + Block( + [ + Expr( + If { + condition: Op { + lhs: Op { + lhs: Var( + Name( + "a", + ), + ), + op: Calc( + Add, + ), + rhs: Var( + Name( + "count", + ), + ), + }, + op: Compare( + Gte, + ), + rhs: Var( + Name( + "b", + ), + ), + }, + then: Block( + [ + Break, + ], + ), + else: Value( + Record( + Record( + {}, + ), + ), + ), + }, + ), + Expr( + Op { + lhs: Var( + Name( + "count", + ), + ), + op: Assign, + rhs: Op { + lhs: Var( + Name( + "count", + ), + ), + op: Calc( + Add, + ), + rhs: Value( + Int( + 1, + ), + ), + }, + }, + ), + ], + ), + ), + Expr( + Var( + Name( + "count", + ), + ), + ), + ], + ), + }, + ), + }, + ], + ), +) diff --git a/src/parser/types.rs b/src/parser/types.rs index ddf780e..ba9f6d6 100644 --- a/src/parser/types.rs +++ b/src/parser/types.rs @@ -37,6 +37,8 @@ pub enum Token { False, Equals, Not, + Loop, + Break, Op(Op), Int(i32), Float(f32), diff --git a/src/snapshots/ayin__tests__loop_counter_9.snap b/src/snapshots/ayin__tests__loop_counter_9.snap new file mode 100644 index 0000000..5390741 --- /dev/null +++ b/src/snapshots/ayin__tests__loop_counter_9.snap @@ -0,0 +1,11 @@ +--- +source: src/lib.rs +expression: result +--- +Ok( + Value( + Int( + 9, + ), + ), +)