//! Switch node. //! use crate::{ expression::Expression, statement::Statement, try_break, visitor::{VisitWith, Visitor, VisitorMut}, StatementList, }; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; /// A case clause inside a [`Switch`] statement, as defined by the [spec]. /// /// Even though every [`Case`] body is a [`StatementList`], it doesn't create a new lexical /// environment. This means any variable declared in a `Case` will be considered as part of the /// lexical environment of the parent [`Switch`] block. /// /// [spec]: https://tc39.es/ecma262/#prod-CaseClause /// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Case { condition: Expression, body: StatementList, } impl Case { /// Creates a `Case` AST node. #[inline] #[must_use] pub const fn new(condition: Expression, body: StatementList) -> Self { Self { condition, body } } /// Gets the condition of the case. #[inline] #[must_use] pub const fn condition(&self) -> &Expression { &self.condition } /// Gets the statement listin the body of the case. #[inline] #[must_use] pub const fn body(&self) -> &StatementList { &self.body } } impl VisitWith for Case { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { try_break!(visitor.visit_expression(&self.condition)); visitor.visit_statement_list(&self.body) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { try_break!(visitor.visit_expression_mut(&mut self.condition)); visitor.visit_statement_list_mut(&mut self.body) } } /// The `switch` statement evaluates an expression, matching the expression's value to a case /// clause, and executes statements associated with that case, as well as statements in cases /// that follow the matching case. /// /// A `switch` statement first evaluates its expression. It then looks for the first case /// clause whose expression evaluates to the same value as the result of the input expression /// (using the strict comparison, `===`) and transfers control to that clause, executing the /// associated statements. (If multiple cases match the provided value, the first case that /// matches is selected, even if the cases are not equal to each other.) /// /// More information: /// - [ECMAScript reference][spec] /// - [MDN documentation][mdn] /// /// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct Switch { val: Expression, cases: Box<[Case]>, default: Option, } impl Switch { /// Creates a `Switch` AST node. #[inline] #[must_use] pub fn new(val: Expression, cases: Box<[Case]>, default: Option) -> Self { Self { val, cases, default, } } /// Gets the value to switch. #[inline] #[must_use] pub const fn val(&self) -> &Expression { &self.val } /// Gets the list of cases for the switch statement. #[inline] #[must_use] pub const fn cases(&self) -> &[Case] { &self.cases } /// Gets the default statement list, if any. #[inline] #[must_use] pub const fn default(&self) -> Option<&StatementList> { self.default.as_ref() } } impl ToIndentedString for Switch { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { let indent = " ".repeat(indentation); let mut buf = format!("switch ({}) {{\n", self.val().to_interned_string(interner)); for e in self.cases().iter() { buf.push_str(&format!( "{} case {}:\n{}", indent, e.condition().to_interned_string(interner), e.body().to_indented_string(interner, indentation + 2) )); } if let Some(ref default) = self.default { buf.push_str(&format!( "{indent} default:\n{}", default.to_indented_string(interner, indentation + 2) )); } buf.push_str(&format!("{indent}}}")); buf } } impl From for Statement { #[inline] fn from(switch: Switch) -> Self { Self::Switch(switch) } } impl VisitWith for Switch { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { try_break!(visitor.visit_expression(&self.val)); for case in self.cases.iter() { try_break!(visitor.visit_case(case)); } if let Some(sl) = &self.default { try_break!(visitor.visit_statement_list(sl)); } ControlFlow::Continue(()) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { try_break!(visitor.visit_expression_mut(&mut self.val)); for case in self.cases.iter_mut() { try_break!(visitor.visit_case_mut(case)); } if let Some(sl) = &mut self.default { try_break!(visitor.visit_statement_list_mut(sl)); } ControlFlow::Continue(()) } }