use crate::{
StatementList,
expression::Expression,
operations::{ContainsSymbol, contains},
scope::Scope,
statement::Statement,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::{fmt::Write as _, ops::ControlFlow};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Case {
condition: Option<Expression>,
body: StatementList,
}
impl Case {
#[inline]
#[must_use]
pub const fn new(condition: Expression, body: StatementList) -> Self {
Self {
condition: Some(condition),
body,
}
}
#[inline]
#[must_use]
pub const fn default(body: StatementList) -> Self {
Self {
condition: None,
body,
}
}
#[inline]
#[must_use]
pub const fn condition(&self) -> Option<&Expression> {
self.condition.as_ref()
}
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
#[inline]
#[must_use]
pub const fn is_default(&self) -> bool {
self.condition.is_none()
}
}
impl VisitWith for Case {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(condition) = &self.condition {
visitor.visit_expression(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>,
{
if let Some(condition) = &mut self.condition {
visitor.visit_expression_mut(condition)?;
}
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Switch {
pub(crate) val: Expression,
pub(crate) cases: Box<[Case]>,
pub(crate) contains_direct_eval: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) scope: Option<Scope>,
}
impl Switch {
#[inline]
#[must_use]
pub fn new(val: Expression, cases: Box<[Case]>) -> Self {
let mut contains_direct_eval = false;
for case in &cases {
contains_direct_eval |= contains(case, ContainsSymbol::DirectEval);
}
Self {
val,
cases,
contains_direct_eval,
scope: None,
}
}
#[inline]
#[must_use]
pub const fn val(&self) -> &Expression {
&self.val
}
#[inline]
#[must_use]
pub const fn cases(&self) -> &[Case] {
&self.cases
}
#[inline]
#[must_use]
pub fn default(&self) -> Option<&StatementList> {
for case in self.cases.as_ref() {
if case.is_default() {
return Some(case.body());
}
}
None
}
#[inline]
#[must_use]
pub const fn scope(&self) -> Option<&Scope> {
self.scope.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 {
if let Some(condition) = e.condition() {
let _ = write!(
buf,
"{indent} case {}:\n{}",
condition.to_interned_string(interner),
e.body().to_indented_string(interner, indentation + 2)
);
} else {
let _ = write!(
buf,
"{indent} default:\n{}",
e.body().to_indented_string(interner, indentation + 2)
);
}
}
let _ = write!(buf, "{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>,
{
visitor.visit_expression(&self.val)?;
for case in &*self.cases {
visitor.visit_case(case)?;
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.val)?;
for case in &mut *self.cases {
visitor.visit_case_mut(case)?;
}
ControlFlow::Continue(())
}
}