diff --git a/boa/src/exec/block/mod.rs b/boa/src/exec/block/mod.rs index ddf4bd3f17d..70b9f56b8f9 100644 --- a/boa/src/exec/block/mod.rs +++ b/boa/src/exec/block/mod.rs @@ -33,6 +33,13 @@ impl Executable for Block { // Early break. break; } + InterpreterState::Continue(_label) => { + // TODO, continue to a label. + + // Continue actually breaks out of the block, because the containing loop will handle the control + // flow. + break; + } _ => { // Continue execution } diff --git a/boa/src/exec/conditional/mod.rs b/boa/src/exec/conditional/mod.rs index 92524612059..3ae982969ff 100644 --- a/boa/src/exec/conditional/mod.rs +++ b/boa/src/exec/conditional/mod.rs @@ -1,7 +1,14 @@ -use super::{Executable, Interpreter}; -use crate::{builtins::Value, syntax::ast::node::If, Result}; +use super::{Executable, Interpreter, InterpreterState}; +use crate::{ + builtins::Value, + syntax::ast::node::{ConditionalOp, Continue, If}, + Result, +}; use std::borrow::Borrow; +#[cfg(test)] +mod tests; + impl Executable for If { fn run(&self, interpreter: &mut Interpreter) -> Result { Ok(if self.cond().run(interpreter)?.borrow().to_boolean() { @@ -13,3 +20,21 @@ impl Executable for If { }) } } + +impl Executable for ConditionalOp { + fn run(&self, interpreter: &mut Interpreter) -> Result { + if self.cond().run(interpreter)?.to_boolean() { + self.if_true().run(interpreter) + } else { + self.if_false().run(interpreter) + } + } +} + +impl Executable for Continue { + fn run(&self, interpreter: &mut Interpreter) -> Result { + interpreter.set_current_state(InterpreterState::Continue(self.label().map(String::from))); + + Ok(Value::undefined()) + } +} diff --git a/boa/src/exec/conditional/tests.rs b/boa/src/exec/conditional/tests.rs new file mode 100644 index 00000000000..cc070fed4d2 --- /dev/null +++ b/boa/src/exec/conditional/tests.rs @@ -0,0 +1,111 @@ +use crate::exec; + +#[test] +fn if_true() { + let scenario = r#" + var a = 0; + if (true) { + a = 100; + } else { + a = 50; + } + a; + "#; + + assert_eq!(&exec(scenario), "100"); +} + +#[test] +fn if_false() { + let scenario = r#" + var a = 0; + if (false) { + a = 100; + } else { + a = 50; + } + a; + "#; + + assert_eq!(&exec(scenario), "50"); +} + +#[test] +fn if_truthy() { + let scenario = r#" + var a = 0; + if ("this value doesn't matter") { + a = 100; + } else { + a = 50; + } + a; + "#; + + assert_eq!(&exec(scenario), "100"); +} + +#[test] +fn if_falsy() { + let scenario = r#" + var a = 0; + if (null) { + a = 100; + } else { + a = 50; + } + a; + "#; + + assert_eq!(&exec(scenario), "50"); +} + +#[test] +fn if_throws() { + let scenario = r#" + var a = 0; + if ((function() { throw new Error("Oh no!") })()) { + a = 100; + } else { + a = 50; + } + a; + "#; + + assert_eq!(&exec(scenario), r#"Error: "Error": "Oh no!""#); +} + +#[test] +fn conditional_op_true() { + let scenario = r#"true ? 100 : 50"#; + + assert_eq!(&exec(scenario), "100"); +} + +#[test] +fn conditional_op_false() { + let scenario = r#"false ? 100 : 50"#; + + assert_eq!(&exec(scenario), "50"); +} + +#[test] +fn conditional_op_truthy() { + let scenario = r#""this text should be irrelevant" ? 100 : 50"#; + + assert_eq!(&exec(scenario), "100"); +} + +#[test] +fn conditional_op_falsy() { + let scenario = r#"null ? 100 : 50"#; + + assert_eq!(&exec(scenario), "50"); +} + +#[test] +fn conditional_op_throws() { + let scenario = r#"(function() { throw new Error("Oh no!") })() ? 100 : 50"#; + + assert_eq!(&exec(scenario), r#"Error: "Error": "Oh no!""#); +} diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index b65ca5e886b..6aad8afc53e 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -43,6 +43,14 @@ impl Executable for ForLoop { interpreter.set_current_state(InterpreterState::Executing); break; } + InterpreterState::Continue(_label) => { + // TODO continue to label. + + // Loops 'consume' continues. + interpreter.set_current_state(InterpreterState::Executing); + + // Allow final_expr to execute. + } InterpreterState::Return => { return Ok(result); } @@ -76,6 +84,13 @@ impl Executable for WhileLoop { interpreter.set_current_state(InterpreterState::Executing); break; } + InterpreterState::Continue(_label) => { + // TODO continue to label. + + // Loops 'consume' continues. + interpreter.set_current_state(InterpreterState::Executing); + continue; + } InterpreterState::Return => { return Ok(result); } @@ -99,6 +114,14 @@ impl Executable for DoWhileLoop { interpreter.set_current_state(InterpreterState::Executing); return Ok(result); } + InterpreterState::Continue(_label) => { + // TODO continue to label. + + // Loops 'consume' continues. + interpreter.set_current_state(InterpreterState::Executing); + + // Continue execution. + } InterpreterState::Return => { return Ok(result); } @@ -117,6 +140,13 @@ impl Executable for DoWhileLoop { interpreter.set_current_state(InterpreterState::Executing); break; } + InterpreterState::Continue(_label) => { + // TODO continue to label. + + // Loops 'consume' continues. + interpreter.set_current_state(InterpreterState::Executing); + continue; + } InterpreterState::Return => { return Ok(result); } diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/exec/iteration/tests.rs index 458546364ed..05ebb2840ec 100644 --- a/boa/src/exec/iteration/tests.rs +++ b/boa/src/exec/iteration/tests.rs @@ -34,6 +34,24 @@ fn while_loop_early_break() { assert_eq!(&exec(scenario), "3"); } +#[test] +fn while_loop_continue() { + let scenario = r#" + let a = 1; + let b = 1; + while (a < 5) { + a++; + if (a >= 3) { + continue; + } + b++; + } + [a, b]; + "#; + + assert_eq!(&exec(scenario), "[ 5, 2 ]"); +} + #[test] fn for_loop_break() { let scenario = r#" @@ -59,13 +77,30 @@ fn for_loop_return() { } } } - + foo(); "#; assert_eq!(&exec(scenario), "3"); } +#[test] +fn for_loop_continue() { + let scenario = r#" + let a = 1; + let b = 1; + for (; a < 5; a++) { + if (a >= 3) { + continue; + } + b++; + } + [a, b]; + "#; + + assert_eq!(&exec(scenario), "[ 5, 3 ]"); +} + #[test] fn do_loop_late_break() { // Ordering with statement before the break. @@ -99,3 +134,21 @@ fn do_loop_early_break() { assert_eq!(&exec(scenario), "3"); } + +#[test] +fn do_loop_continue() { + let scenario = r#" + let a = 1; + let b = 1; + do { + a++; + if (a >= 3) { + continue; + } + b++; + } while (a < 5); + [a, b]; + "#; + + assert_eq!(&exec(scenario), "[ 5, 2 ]"); +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index af9f92f382b..1c2373d3031 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -50,6 +50,7 @@ pub(crate) enum InterpreterState { Executing, Return, Break(Option), + Continue(Option), } /// A Javascript intepreter @@ -424,8 +425,8 @@ impl Executable for Node { } Node::Try(ref try_node) => try_node.run(interpreter), Node::Break(ref break_node) => break_node.run(interpreter), - Node::ConditionalOp(_) => unimplemented!("ConditionalOp"), - Node::Continue(_) => unimplemented!("Continue"), + Node::ConditionalOp(ref conditional_op_node) => conditional_op_node.run(interpreter), + Node::Continue(ref continue_node) => continue_node.run(interpreter), } } } diff --git a/boa/src/exec/statement_list.rs b/boa/src/exec/statement_list.rs index bb358cb8869..6a053df9aeb 100644 --- a/boa/src/exec/statement_list.rs +++ b/boa/src/exec/statement_list.rs @@ -25,6 +25,13 @@ impl Executable for StatementList { // Early break. break; } + InterpreterState::Continue(_label) => { + // TODO, continue to a label. + + // Continue actually breaks out of the block, because the containing loop will handle the control + // flow. + break; + } _ => { // Continue execution } diff --git a/boa/src/exec/switch/mod.rs b/boa/src/exec/switch/mod.rs index 8eb5dd1c852..d0242780e26 100644 --- a/boa/src/exec/switch/mod.rs +++ b/boa/src/exec/switch/mod.rs @@ -32,6 +32,12 @@ impl Executable for Switch { interpreter.set_current_state(InterpreterState::Executing); break; } + InterpreterState::Continue(_label) => { + // TODO continue to label. + + // Relinquish control to containing loop. + break; + } _ => { // Continuing execution / falling through to next case statement(s). fall_through = true; diff --git a/boa/src/exec/switch/tests.rs b/boa/src/exec/switch/tests.rs index ead275bc836..423ddda3268 100644 --- a/boa/src/exec/switch/tests.rs +++ b/boa/src/exec/switch/tests.rs @@ -9,7 +9,7 @@ fn single_case_switch() { a = 20; break; } - + a; "#; assert_eq!(&exec(scenario), "20"); @@ -21,7 +21,7 @@ fn no_cases_switch() { let a = 10; switch (a) { } - + a; "#; assert_eq!(&exec(scenario), "10"); @@ -36,7 +36,7 @@ fn no_true_case_switch() { a = 15; break; } - + a; "#; assert_eq!(&exec(scenario), "10"); @@ -54,7 +54,7 @@ fn two_case_switch() { a = 20; break; } - + a; "#; assert_eq!(&exec(scenario), "20"); @@ -73,7 +73,7 @@ fn two_case_no_break_switch() { b = 150; break; } - + a + b; "#; assert_eq!(&exec(scenario), "300"); @@ -95,7 +95,7 @@ fn three_case_partial_fallthrough() { b = 1000; break; } - + a + b; "#; assert_eq!(&exec(scenario), "300"); @@ -113,7 +113,7 @@ fn default_taken_switch() { default: a = 70; } - + a; "#; assert_eq!(&exec(scenario), "70"); @@ -131,7 +131,7 @@ fn default_not_taken_switch() { default: a = 70; } - + a; "#; assert_eq!(&exec(scenario), "150"); @@ -149,7 +149,7 @@ fn string_switch() { default: a = "hi"; } - + a; "#; assert_eq!(&exec(scenario), "\"world\""); @@ -194,7 +194,7 @@ fn bigger_switch_example() { break; case 6: b = "Sun"; - break; + break; }} b; @@ -206,3 +206,21 @@ fn bigger_switch_example() { assert_eq!(&exec(&scenario), val); } } + +#[test] +fn switch_continue() { + let scenario = r#" + let a = 0; + while (a < 10) { + switch (a) { + case 5: + a = 100; + continue; + } + a++; + } + + a; + "#; + assert_eq!(&exec(scenario), "100"); +}