190 lines
5.7 KiB
Rust
190 lines
5.7 KiB
Rust
//! 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<V::BreakTy>
|
|
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<V::BreakTy>
|
|
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<StatementList>,
|
|
}
|
|
|
|
impl Switch {
|
|
/// Creates a `Switch` AST node.
|
|
#[inline]
|
|
#[must_use]
|
|
pub fn new(val: Expression, cases: Box<[Case]>, default: Option<StatementList>) -> 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<Switch> 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<V::BreakTy>
|
|
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<V::BreakTy>
|
|
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(())
|
|
}
|
|
}
|