From 3596af11dcf1a786ed2bcbdcd410e163dcb478f1 Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Fri, 12 Jul 2024 20:18:36 +0900 Subject: [PATCH 1/9] Add support for pasing `while` statement --- momonga/src/ast.rs | 9 +++++++ momonga/src/eval.rs | 1 + momonga/src/momonga.pest | 5 +++- momonga/src/parser.rs | 53 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/momonga/src/ast.rs b/momonga/src/ast.rs index 0532336..31a74be 100644 --- a/momonga/src/ast.rs +++ b/momonga/src/ast.rs @@ -10,6 +10,8 @@ pub enum Stmt { #[allow(clippy::enum_variant_names)] ForStmt(ForStmt), #[allow(clippy::enum_variant_names)] + WhileStmt(WhileStmt), + #[allow(clippy::enum_variant_names)] VarStmt(VarStmt), #[allow(clippy::enum_variant_names)] ExprStmt(ExprStmt), @@ -56,6 +58,13 @@ pub enum ForStmtInit { pub type ForStmtCond = Expr; pub type ForStmtAfterthought = Expr; +#[derive(Debug, PartialEq, Clone)] +pub struct WhileStmt { + pub cond: WhileStmtCond, + pub block: BlockStmt, +} +pub type WhileStmtCond = Expr; + #[derive(Debug, PartialEq, Clone)] pub struct VarStmt { pub ident: Ident, diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index 077302c..091b804 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -40,6 +40,7 @@ fn eval_block_stmt<'a>(block_stmt: &'a BlockStmt, env: Rc>>) -> Stmt::FuncDecl(func_decl) => eval_func_decl(func_decl, Rc::clone(&env_block)), Stmt::IfStmt(if_stmt) => eval_if_stmt(if_stmt, Rc::clone(&env_block)), Stmt::ForStmt(for_stmt) => eval_for_stmt(for_stmt, Rc::clone(&env_block)), + Stmt::WhileStmt(while_stmt) => todo!(), Stmt::VarStmt(var_stmt) => eval_var_stmt(var_stmt, Rc::clone(&env_block)), Stmt::ExprStmt(expr_stmt) => eval_expr_stmt(expr_stmt, Rc::clone(&env_block)), Stmt::ContinueStmt => Err(JumpStmt::Continue), diff --git a/momonga/src/momonga.pest b/momonga/src/momonga.pest index b4fa725..f3ac9ed 100644 --- a/momonga/src/momonga.pest +++ b/momonga/src/momonga.pest @@ -1,6 +1,6 @@ program = { SOI ~ wc* ~ stmt* ~ EOI } -stmt = { block_stmt | func_decl | if_stmt | for_stmt | var_stmt | expr_stmt | continue_stmt | break_stmt | return_stmt } +stmt = { block_stmt | func_decl | if_stmt | for_stmt | while_stmt | var_stmt | expr_stmt | continue_stmt | break_stmt | return_stmt } block_stmt = { "{" ~ wc* ~ stmt* ~ "}" ~ wc* } @@ -14,6 +14,9 @@ for_stmt_init = { ("var" ~ wc+ ~ IDENT ~ wc* ~ "=" ~ wc*)? ~ expr } for_stmt_cond = { expr } for_stmt_afterthought = { expr } +while_stmt = { "while" ~ wc* ~ "(" ~ wc* ~ while_stmt_cond ~ ")" ~ wc* ~ block_stmt } +while_stmt_cond = { expr } + var_stmt = { "var" ~ wc+ ~ IDENT ~ wc* ~ ("=" ~ wc* ~ expr)? ~ ";" ~ wc* } expr_stmt = _{ expr ~ ";" ~ wc* } diff --git a/momonga/src/parser.rs b/momonga/src/parser.rs index c144367..7446b77 100644 --- a/momonga/src/parser.rs +++ b/momonga/src/parser.rs @@ -87,6 +87,7 @@ impl AstBuilder { Rule::func_decl => Ok(Stmt::FuncDecl(self.func_decl(unknown_pair)?)), Rule::if_stmt => Ok(Stmt::IfStmt(self.if_stmt(unknown_pair)?)), Rule::for_stmt => Ok(Stmt::ForStmt(self.for_stmt(unknown_pair)?)), + Rule::while_stmt => Ok(Stmt::WhileStmt(self.while_stmt(unknown_pair)?)), Rule::var_stmt => Ok(Stmt::VarStmt(self.var_stmt(unknown_pair)?)), Rule::expr => Ok(Stmt::ExprStmt(self.expr(unknown_pair)?)), Rule::continue_stmt => { @@ -308,6 +309,16 @@ impl AstBuilder { Ok(block_stmt) } + fn while_stmt(&mut self, while_stmt_pair: Pair) -> Result { + let mut while_stmt_inner = while_stmt_pair.into_inner(); + let cond = while_stmt_inner.next().map(|p| self.expr(p)).unwrap()?; + let block = while_stmt_inner.next().map(|p| self.block_stmt(p)).unwrap()?; + Ok(WhileStmt { + cond, + block + }) + } + fn var_stmt(&self, pair: Pair) -> Result { let mut var_stmt_inner = pair.into_inner(); let ident_pair = var_stmt_inner.next().unwrap(); @@ -585,7 +596,12 @@ mod tests { "#, Err(ParseError::PestParser), ), - ]; + ( + r#" + while(){} + "#, + Err(ParseError::PestParser), + ), ]; for (src, expected) in tests { assert_eq!(parse(src), expected, "Failed in test case: {}", src); @@ -1142,6 +1158,41 @@ mod tests { } } + #[test] + fn while_stmt_ast_is_built_correctly() { + let tests = [ + ( + r#" + while (true) {} + "#, + Ok(vec![ + Stmt::WhileStmt(WhileStmt { + cond: Expr::literal_bool(true), + block: vec![] + }) + ]) + ), + ( + r#" + while (true) { + 42; + } + "#, + Ok(vec![ + Stmt::WhileStmt(WhileStmt { + cond: Expr::literal_bool(true), + block: vec![ + Stmt::ExprStmt(Expr::literal_int(42)) + ] + }) + ]) + ), ]; + + for (src, expected) in tests { + assert_eq!(parse(src), expected, "Failed in test case: {}", src); + } + } + #[test] fn var_stmt_ast_is_built_correctly() { let tests = [ From f90d1d4eed811279b4dd48943e2948f5a38bbdbe Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Fri, 12 Jul 2024 20:40:50 +0900 Subject: [PATCH 2/9] Upgrade Rust version to 1.74 in Github Actions workflow --- .github/workflows/cicd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml index c8b59c5..d7ac2d3 100644 --- a/.github/workflows/cicd.yaml +++ b/.github/workflows/cicd.yaml @@ -21,7 +21,7 @@ jobs: node-version: 18 - uses: actions-rs/toolchain@v1 # TODO: Not maintained. Consider using an alternative. with: - toolchain: 1.72 + toolchain: 1.74 target: wasm32-unknown-unknown override: true components: clippy From 9584b4625b4700358b345f586bb1d8c49aa058eb Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Fri, 12 Jul 2024 20:51:23 +0900 Subject: [PATCH 3/9] Fix linting errors --- momonga/src/eval.rs | 8 ++++---- momonga/tests/integration_test.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index 091b804..169ca35 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -17,7 +17,7 @@ pub enum JumpStmt<'a> { Error(EvalError), } -const MAX_ABS_INT: u64 = std::i64::MIN.unsigned_abs(); // TODO: Reconsider how to handle value overflow +const MAX_ABS_INT: u64 = i64::MIN.unsigned_abs(); // TODO: Reconsider how to handle value overflow pub fn eval<'a>( program: &'a Program, @@ -40,7 +40,7 @@ fn eval_block_stmt<'a>(block_stmt: &'a BlockStmt, env: Rc>>) -> Stmt::FuncDecl(func_decl) => eval_func_decl(func_decl, Rc::clone(&env_block)), Stmt::IfStmt(if_stmt) => eval_if_stmt(if_stmt, Rc::clone(&env_block)), Stmt::ForStmt(for_stmt) => eval_for_stmt(for_stmt, Rc::clone(&env_block)), - Stmt::WhileStmt(while_stmt) => todo!(), + Stmt::WhileStmt(_while_stmt) => todo!(), Stmt::VarStmt(var_stmt) => eval_var_stmt(var_stmt, Rc::clone(&env_block)), Stmt::ExprStmt(expr_stmt) => eval_expr_stmt(expr_stmt, Rc::clone(&env_block)), Stmt::ContinueStmt => Err(JumpStmt::Continue), @@ -214,12 +214,12 @@ fn eval_expr<'a>(expr: &'a Expr, env: Rc>>) -> EvalExprResult<'a PrefixOpKind::Neg => { if let Expr::Literal(Literal::Int(int)) = **rhs { if int == MAX_ABS_INT { - return Ok(Rc::new(RefCell::new(Value::Int(std::i64::MIN)))); + return Ok(Rc::new(RefCell::new(Value::Int(i64::MIN)))); }; }; match *eval_expr(rhs, env)?.borrow() { Value::Int(int) => { - if int == std::i64::MIN { + if int == i64::MIN { return Err(JumpStmt::Error(EvalError::OutOfRange)); // Attempt to nagate i64 min } diff --git a/momonga/tests/integration_test.rs b/momonga/tests/integration_test.rs index 8c8877e..bb088b9 100644 --- a/momonga/tests/integration_test.rs +++ b/momonga/tests/integration_test.rs @@ -135,7 +135,7 @@ fn prefix_operator_is_interpreted_correctly() { r#" -9223372036854775808; // Min value of Integer type "#, - Some(std::i64::MIN.to_string()), + Some(i64::MIN.to_string()), ), ( r#" @@ -153,7 +153,7 @@ fn prefix_operator_is_interpreted_correctly() { r#" +-9223372036854775808; // Attempt to apply + operator to the min of Integer type "#, - Some(std::i64::MIN.to_string()), + Some(i64::MIN.to_string()), ), ( r#" From e4055560bbdea09d4d9e60ec0896ef365d3f51f9 Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Sat, 13 Jul 2024 07:06:49 +0900 Subject: [PATCH 4/9] Fix scoping in `For` statement --- momonga/src/eval.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index 169ca35..482633f 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -125,18 +125,17 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt afterthought, block, } = for_stmt; - let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); match init { - Some(ForStmtInit::Var(var_stmt)) => eval_var_stmt(var_stmt, Rc::clone(&env_block))?, - Some(ForStmtInit::Expr(expr_stmt)) => eval_expr_stmt(expr_stmt, env)?, + Some(ForStmtInit::Var(var_stmt)) => eval_var_stmt(var_stmt, Rc::clone(&env))?, + Some(ForStmtInit::Expr(expr_stmt)) => eval_expr_stmt(expr_stmt, Rc::clone(&env))?, _ => todo!(), // TODO: Define how to handle this case }; let mut result = Ok(None); loop { let cond = match cond { - Some(cond) => match *eval_expr(cond, Rc::clone(&env_block))?.borrow() { + Some(cond) => match *eval_expr(cond, Rc::clone(&env))?.borrow() { Value::Bool(bool) => bool, _ => todo!(), // TODO: Define how to handle this case }, @@ -147,9 +146,9 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt break; } - result = match eval_block_stmt(block, Rc::clone(&env_block)) { + result = match eval_block_stmt(block, Rc::clone(&env)) { Err(JumpStmt::Continue) => { - eval_for_stmt_afterthought(afterthought, Rc::clone(&env_block))?; + eval_for_stmt_afterthought(afterthought, Rc::clone(&env))?; result = Ok(None); continue; } @@ -159,7 +158,7 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt default => default, }; - eval_for_stmt_afterthought(afterthought, Rc::clone(&env_block))?; + eval_for_stmt_afterthought(afterthought, Rc::clone(&env))?; } result } From 6823867cef347358490510c739d0af8eeaa6f4ff Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Sat, 13 Jul 2024 07:37:03 +0900 Subject: [PATCH 5/9] Revert "Fix scoping in `For` statement" This reverts commit e4055560bbdea09d4d9e60ec0896ef365d3f51f9. --- momonga/src/eval.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index 482633f..169ca35 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -125,17 +125,18 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt afterthought, block, } = for_stmt; + let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); match init { - Some(ForStmtInit::Var(var_stmt)) => eval_var_stmt(var_stmt, Rc::clone(&env))?, - Some(ForStmtInit::Expr(expr_stmt)) => eval_expr_stmt(expr_stmt, Rc::clone(&env))?, + Some(ForStmtInit::Var(var_stmt)) => eval_var_stmt(var_stmt, Rc::clone(&env_block))?, + Some(ForStmtInit::Expr(expr_stmt)) => eval_expr_stmt(expr_stmt, env)?, _ => todo!(), // TODO: Define how to handle this case }; let mut result = Ok(None); loop { let cond = match cond { - Some(cond) => match *eval_expr(cond, Rc::clone(&env))?.borrow() { + Some(cond) => match *eval_expr(cond, Rc::clone(&env_block))?.borrow() { Value::Bool(bool) => bool, _ => todo!(), // TODO: Define how to handle this case }, @@ -146,9 +147,9 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt break; } - result = match eval_block_stmt(block, Rc::clone(&env)) { + result = match eval_block_stmt(block, Rc::clone(&env_block)) { Err(JumpStmt::Continue) => { - eval_for_stmt_afterthought(afterthought, Rc::clone(&env))?; + eval_for_stmt_afterthought(afterthought, Rc::clone(&env_block))?; result = Ok(None); continue; } @@ -158,7 +159,7 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt default => default, }; - eval_for_stmt_afterthought(afterthought, Rc::clone(&env))?; + eval_for_stmt_afterthought(afterthought, Rc::clone(&env_block))?; } result } From 1aec415aac74edde8854d81ecf92c13beb540eb6 Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Sat, 13 Jul 2024 07:51:27 +0900 Subject: [PATCH 6/9] Fix scoping by moving creation of block scope outside of eval_block_stmt function --- momonga/src/eval.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index 169ca35..de768d8 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -31,24 +31,26 @@ pub fn eval<'a>( } fn eval_block_stmt<'a>(block_stmt: &'a BlockStmt, env: Rc>>) -> EvalStmtResult<'a> { - let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); let mut result = Ok(None); for stmt in block_stmt { result = match stmt { - Stmt::BlockStmt(block_stmt) => eval_block_stmt(block_stmt, Rc::clone(&env_block)), - Stmt::FuncDecl(func_decl) => eval_func_decl(func_decl, Rc::clone(&env_block)), - Stmt::IfStmt(if_stmt) => eval_if_stmt(if_stmt, Rc::clone(&env_block)), - Stmt::ForStmt(for_stmt) => eval_for_stmt(for_stmt, Rc::clone(&env_block)), + Stmt::BlockStmt(block_stmt) => { + let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); + eval_block_stmt(block_stmt, Rc::clone(&env_block)) + }, + Stmt::FuncDecl(func_decl) => eval_func_decl(func_decl, Rc::clone(&env)), + Stmt::IfStmt(if_stmt) => eval_if_stmt(if_stmt, Rc::clone(&env)), + Stmt::ForStmt(for_stmt) => eval_for_stmt(for_stmt, Rc::clone(&env)), Stmt::WhileStmt(_while_stmt) => todo!(), - Stmt::VarStmt(var_stmt) => eval_var_stmt(var_stmt, Rc::clone(&env_block)), - Stmt::ExprStmt(expr_stmt) => eval_expr_stmt(expr_stmt, Rc::clone(&env_block)), + Stmt::VarStmt(var_stmt) => eval_var_stmt(var_stmt, Rc::clone(&env)), + Stmt::ExprStmt(expr_stmt) => eval_expr_stmt(expr_stmt, Rc::clone(&env)), Stmt::ContinueStmt => Err(JumpStmt::Continue), Stmt::BreakStmt => Err(JumpStmt::Break), Stmt::ReturnStmt(return_stmt) => { let ReturnStmt { expr } = return_stmt; match expr { - Some(expr) => Err(JumpStmt::Return(eval_expr(expr, Rc::clone(&env_block))?)), + Some(expr) => Err(JumpStmt::Return(eval_expr(expr, Rc::clone(&env))?)), None => Err(JumpStmt::Return(Rc::new(RefCell::new(Value::None)))), } } @@ -92,12 +94,14 @@ fn eval_if_stmt<'a>(if_stmt: &'a IfStmt, env: Rc>>) -> EvalStmtR else_clause, } = if_stmt; + let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); + match *eval_expr(condition, Rc::clone(&env))?.borrow() { Value::Bool(bool) => { if bool { - eval_block_stmt(block, env) + eval_block_stmt(block, env_block) } else if let Some(if_stmt_else_clause) = else_clause { - eval_if_stmt_else_clause(if_stmt_else_clause, env) + eval_if_stmt_else_clause(if_stmt_else_clause, env_block) } else { Ok(None) } From 0b25b6b75cd0c594af5056c80fcd79d858186044 Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Tue, 16 Jul 2024 18:29:58 +0900 Subject: [PATCH 7/9] Add support for evaluating `While` statement --- momonga/src/eval.rs | 48 +++++++++++++++++++++++++-- momonga/src/parser.rs | 55 ++++++++++++++++++++++++------- momonga/tests/integration_test.rs | 48 +++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/momonga/src/eval.rs b/momonga/src/eval.rs index de768d8..6f0fb00 100644 --- a/momonga/src/eval.rs +++ b/momonga/src/eval.rs @@ -42,7 +42,7 @@ fn eval_block_stmt<'a>(block_stmt: &'a BlockStmt, env: Rc>>) -> Stmt::FuncDecl(func_decl) => eval_func_decl(func_decl, Rc::clone(&env)), Stmt::IfStmt(if_stmt) => eval_if_stmt(if_stmt, Rc::clone(&env)), Stmt::ForStmt(for_stmt) => eval_for_stmt(for_stmt, Rc::clone(&env)), - Stmt::WhileStmt(_while_stmt) => todo!(), + Stmt::WhileStmt(while_stmt) => eval_while_stmt(while_stmt, Rc::clone(&env)), Stmt::VarStmt(var_stmt) => eval_var_stmt(var_stmt, Rc::clone(&env)), Stmt::ExprStmt(expr_stmt) => eval_expr_stmt(expr_stmt, Rc::clone(&env)), Stmt::ContinueStmt => Err(JumpStmt::Continue), @@ -158,7 +158,8 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc>>) -> EvalSt continue; } Err(JumpStmt::Break) => { - return Ok(None); + result = Ok(None); + break; } default => default, }; @@ -178,6 +179,37 @@ fn eval_for_stmt_afterthought<'a>( Ok(None) } +fn eval_while_stmt<'a>(while_stmt: &'a WhileStmt, env: Rc>>) -> EvalStmtResult<'a> { + let WhileStmt { cond, block } = while_stmt; + + let mut result = Ok(None); + let env_block = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&env))))); + + loop { + let cond = match *eval_expr(cond, Rc::clone(&env_block))?.borrow() { + Value::Bool(bool) => bool, + _ => return Err(JumpStmt::Error(EvalError::Type)), + }; + + if !cond { break; } + + result = match eval_block_stmt(block, Rc::clone(&env_block)) { + Err(JumpStmt::Continue) => { + result = Ok(None); + continue; + }, + Err(JumpStmt::Break) => { + result = Ok(None); + break; + }, + default => default, + } + } + + result + +} + fn eval_var_stmt<'a>(var_stmt: &'a VarStmt, env: Rc>>) -> EvalStmtResult<'a> { let VarStmt { ident: Ident { name }, @@ -684,6 +716,18 @@ mod tests { ), Ok(Some(Rc::new(RefCell::new(Value::Int(9))))) ); + // WhileStmt + assert_eq!( + eval( + // while(true) {break;} + &vec![Stmt::WhileStmt(WhileStmt { + cond: Expr::literal_bool(true), + block: vec![Stmt::BreakStmt] + })], + Rc::new(RefCell::new(Env::new_with_builtins())) + ), + Ok(None) + ); // VarStmt assert_eq!( eval( diff --git a/momonga/src/parser.rs b/momonga/src/parser.rs index 7446b77..e0a4200 100644 --- a/momonga/src/parser.rs +++ b/momonga/src/parser.rs @@ -209,7 +209,7 @@ impl AstBuilder { Rule::for_stmt_afterthought => { let afterthought = Some(self.expr(unknown_pair)?); let block = - self.block_stmt_of_for(for_stmt_inner.next().unwrap())?; + self.block_stmt_of_loop(for_stmt_inner.next().unwrap())?; Ok(ForStmt { init, cond, @@ -221,14 +221,14 @@ impl AstBuilder { init, cond, afterthought: None, - block: self.block_stmt_of_for(unknown_pair)?, + block: self.block_stmt_of_loop(unknown_pair)?, }), _ => unreachable!(), } } Rule::for_stmt_afterthought => { let afterthought = Some(self.expr(unknown_pair)?); - let block = self.block_stmt_of_for(for_stmt_inner.next().unwrap())?; + let block = self.block_stmt_of_loop(for_stmt_inner.next().unwrap())?; Ok(ForStmt { init, cond: None, @@ -240,7 +240,7 @@ impl AstBuilder { init, cond: None, afterthought: None, - block: self.block_stmt_of_for(unknown_pair)?, + block: self.block_stmt_of_loop(unknown_pair)?, }), _ => unreachable!(), } @@ -251,7 +251,7 @@ impl AstBuilder { match unknown_pair.as_rule() { Rule::for_stmt_afterthought => { let afterthought = Some(self.expr(unknown_pair)?); - let block = self.block_stmt_of_for(for_stmt_inner.next().unwrap())?; + let block = self.block_stmt_of_loop(for_stmt_inner.next().unwrap())?; Ok(ForStmt { init: None, cond, @@ -263,14 +263,14 @@ impl AstBuilder { init: None, cond, afterthought: None, - block: self.block_stmt_of_for(unknown_pair)?, + block: self.block_stmt_of_loop(unknown_pair)?, }), _ => unreachable!(), } } Rule::for_stmt_afterthought => { let afterthought = Some(self.expr(unknown_pair)?); - let block = self.block_stmt_of_for(for_stmt_inner.next().unwrap())?; + let block = self.block_stmt_of_loop(for_stmt_inner.next().unwrap())?; Ok(ForStmt { init: None, cond: None, @@ -282,7 +282,7 @@ impl AstBuilder { init: None, cond: None, afterthought: None, - block: self.block_stmt_of_for(unknown_pair)?, + block: self.block_stmt_of_loop(unknown_pair)?, }), _ => unreachable!(), } @@ -301,7 +301,7 @@ impl AstBuilder { } } - fn block_stmt_of_for(&mut self, block_stmt_pair: Pair) -> Result { + fn block_stmt_of_loop(&mut self, block_stmt_pair: Pair) -> Result { let block_stmt = self.block_stmt(block_stmt_pair)?; if let AstBuildFlow::Continue | AstBuildFlow::Break = self.flow { self.flow = AstBuildFlow::Value; @@ -312,7 +312,7 @@ impl AstBuilder { fn while_stmt(&mut self, while_stmt_pair: Pair) -> Result { let mut while_stmt_inner = while_stmt_pair.into_inner(); let cond = while_stmt_inner.next().map(|p| self.expr(p)).unwrap()?; - let block = while_stmt_inner.next().map(|p| self.block_stmt(p)).unwrap()?; + let block = while_stmt_inner.next().map(|p| self.block_stmt_of_loop(p)).unwrap()?; Ok(WhileStmt { cond, block @@ -1186,7 +1186,40 @@ mod tests { ] }) ]) - ), ]; + ), + ( + r#" + while (true) { + break; + continue; // Ignored by parser + } + "#, + Ok(vec![ + Stmt::WhileStmt(WhileStmt { + cond: Expr::literal_bool(true), + block: vec![ + Stmt::BreakStmt + ] + }) + ]) + ), + ( + r#" + while (true) { + continue; + break; // Ignored by parser + } + "#, + Ok(vec![ + Stmt::WhileStmt(WhileStmt { + cond: Expr::literal_bool(true), + block: vec![ + Stmt::ContinueStmt + ] + }) + ]) + ), + ]; for (src, expected) in tests { assert_eq!(parse(src), expected, "Failed in test case: {}", src); diff --git a/momonga/tests/integration_test.rs b/momonga/tests/integration_test.rs index bb088b9..a4f92fe 100644 --- a/momonga/tests/integration_test.rs +++ b/momonga/tests/integration_test.rs @@ -1329,6 +1329,54 @@ fn for_statement_controls_flow_correctly() { } } +#[test] +fn while_statement_controls_flow_correctly() { + let tests = [ + ( + r#" + var x = 1; + while (x < 10) { + x = x + 1; + } + x; + "#, + Some("10".to_string()), + ), + ( + r#" + var x = 1; + while (x < 10) { + x = x + 1; + if (x == 3 ) { + break; + x = 100; // Not executed + } + } + x; + "#, + Some("3".to_string()), + ), + ( + r#" + var x = 1; + while (x < 10) { + x = x + 1; + if (true) { + continue; + x = 100; // Not executed + } + } + x; + "#, + Some("10".to_string()), + ), + ]; + + for (src, expected) in tests { + assert_eq!(interpret(src), expected, "Failed in test case: {}", src); + } +} + #[test] fn builtin_function_len_works() { let tests = [ From 3122bde160566ebaac8f6d7ac878b405282f03ac Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Tue, 16 Jul 2024 18:47:04 +0900 Subject: [PATCH 8/9] Add `While` statement snippet --- src/constants.ts | 5 +++++ src/types/types.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index ca91d39..eb7907d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -26,6 +26,11 @@ export const snippets: Snippet[] = [ label: "For Statement", code: `for(var i = 0; i < 10; i = i + 1) {\n if (i == 3) {\n continue; // Move on to the next iteration\n }\n\n print(i);\n\n if (i == 7) {\n break; // Break out of the loop\n }\n}\n`, }, + { + key: "whileStatement", + label: "While Statement", + code: `var x = 0;\n\nwhile(x < 10) {\n x = x + 1;\n if (x == 3) {\n continue; // Move on to the next iteration\n }\n\n if (x == 7) {\n break; // Break out of the loop\n }\n\n print(x);\n}\n`, + }, { key: "array", label: "Array", diff --git a/src/types/types.ts b/src/types/types.ts index 35fdaf4..f4d3000 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -23,6 +23,7 @@ export type SnippetKey = | "functionDeclarationAndCall" | "ifStatement" | "forStatement" + | "whileStatement" | "blockScope" | "lexicalScoping" | "recursiveFunction"; From 80116294022babb01cbfe44376e89e05a471ad07 Mon Sep 17 00:00:00 2001 From: radish19_MBA2020 Date: Tue, 16 Jul 2024 18:54:55 +0900 Subject: [PATCH 9/9] Add description for `While` statement --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c46add..96b0590 100644 --- a/README.md +++ b/README.md @@ -267,12 +267,27 @@ for (var i = 0; i < 100; i = i + 1) { Curly braces are always required. ```JavaScript -for (var i = 0; i < 100; i = i + 1) ; // Syntax error +for (;;) ; // Syntax error ``` -### While Statement +#### While Statement -:warning: Under Development +```JavaScript +while (x < 100) { + if (x % 2 == 0 ) { + continue; + } + odd_sum = odd_sum + x; +} +``` + +`break` statement can also be used in while statement. + +Curly braces are always required. + +```JavaScript +while (true) ; // Syntax error +``` ### Function Declaraion and Call Operator