Skip to content

Commit

Permalink
Merge pull request #22 from radish19/feature/momonga/add-while-statement
Browse files Browse the repository at this point in the history
Add support for `While` statement
  • Loading branch information
derarion authored Jul 16, 2024
2 parents 6ae60cf + 8011629 commit 04d4f23
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 16 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 46 additions & 2 deletions momonga/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn eval_block_stmt<'a>(block_stmt: &'a BlockStmt, env: Rc<RefCell<Env<'a>>>) ->
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),
Expand Down Expand Up @@ -158,7 +158,8 @@ fn eval_for_stmt<'a>(for_stmt: &'a ForStmt, env: Rc<RefCell<Env<'a>>>) -> EvalSt
continue;
}
Err(JumpStmt::Break) => {
return Ok(None);
result = Ok(None);
break;
}
default => default,
};
Expand All @@ -178,6 +179,37 @@ fn eval_for_stmt_afterthought<'a>(
Ok(None)
}

fn eval_while_stmt<'a>(while_stmt: &'a WhileStmt, env: Rc<RefCell<Env<'a>>>) -> 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<RefCell<Env<'a>>>) -> EvalStmtResult<'a> {
let VarStmt {
ident: Ident { name },
Expand Down Expand Up @@ -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(
Expand Down
55 changes: 44 additions & 11 deletions momonga/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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!(),
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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!(),
}
Expand All @@ -301,7 +301,7 @@ impl AstBuilder {
}
}

fn block_stmt_of_for(&mut self, block_stmt_pair: Pair<Rule>) -> Result<BlockStmt, ParseError> {
fn block_stmt_of_loop(&mut self, block_stmt_pair: Pair<Rule>) -> Result<BlockStmt, ParseError> {
let block_stmt = self.block_stmt(block_stmt_pair)?;
if let AstBuildFlow::Continue | AstBuildFlow::Break = self.flow {
self.flow = AstBuildFlow::Value;
Expand All @@ -312,7 +312,7 @@ impl AstBuilder {
fn while_stmt(&mut self, while_stmt_pair: Pair<Rule>) -> Result<WhileStmt, ParseError> {
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
Expand Down Expand Up @@ -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);
Expand Down
48 changes: 48 additions & 0 deletions momonga/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SnippetKey =
| "functionDeclarationAndCall"
| "ifStatement"
| "forStatement"
| "whileStatement"
| "blockScope"
| "lexicalScoping"
| "recursiveFunction";

0 comments on commit 04d4f23

Please sign in to comment.