boa/syntax/ast/node/switch/
mod.rs

1//! Switch node.
2//!
3use crate::{
4    exec::{Executable, InterpreterState},
5    gc::{Finalize, Trace},
6    syntax::ast::node::Node,
7    Context, JsResult, JsValue,
8};
9use std::fmt;
10
11use crate::syntax::ast::node::StatementList;
12
13#[cfg(feature = "deser")]
14use serde::{Deserialize, Serialize};
15
16#[cfg(test)]
17mod tests;
18
19#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
20#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
21pub struct Case {
22    condition: Node,
23    body: StatementList,
24}
25
26impl Case {
27    /// Creates a `Case` AST node.
28    pub fn new<C, B>(condition: C, body: B) -> Self
29    where
30        C: Into<Node>,
31        B: Into<StatementList>,
32    {
33        Self {
34            condition: condition.into(),
35            body: body.into(),
36        }
37    }
38
39    /// Gets the condition of the case.
40    pub fn condition(&self) -> &Node {
41        &self.condition
42    }
43
44    /// Gets the statement listin the body of the case.
45    pub fn body(&self) -> &StatementList {
46        &self.body
47    }
48}
49
50/// The `switch` statement evaluates an expression, matching the expression's value to a case
51/// clause, and executes statements associated with that case, as well as statements in cases
52/// that follow the matching case.
53///
54/// A `switch` statement first evaluates its expression. It then looks for the first case
55/// clause whose expression evaluates to the same value as the result of the input expression
56/// (using the strict comparison, `===`) and transfers control to that clause, executing the
57/// associated statements. (If multiple cases match the provided value, the first case that
58/// matches is selected, even if the cases are not equal to each other.)
59///
60/// More information:
61///  - [ECMAScript reference][spec]
62///  - [MDN documentation][mdn]
63///
64/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
65/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
66#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
67#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
68pub struct Switch {
69    val: Box<Node>,
70    cases: Box<[Case]>,
71    default: Option<StatementList>,
72}
73
74impl Switch {
75    /// Creates a `Switch` AST node.
76    pub fn new<V, C, D>(val: V, cases: C, default: Option<D>) -> Self
77    where
78        V: Into<Node>,
79        C: Into<Box<[Case]>>,
80        D: Into<StatementList>,
81    {
82        Self {
83            val: Box::new(val.into()),
84            cases: cases.into(),
85            default: default.map(D::into),
86        }
87    }
88
89    /// Gets the value to switch.
90    pub fn val(&self) -> &Node {
91        &self.val
92    }
93
94    /// Gets the list of cases for the switch statement.
95    pub fn cases(&self) -> &[Case] {
96        &self.cases
97    }
98
99    /// Gets the default statement list, if any.
100    pub fn default(&self) -> Option<&[Node]> {
101        self.default.as_ref().map(StatementList::items)
102    }
103
104    /// Implements the display formatting with indentation.
105    pub(in crate::syntax::ast::node) fn display(
106        &self,
107        f: &mut fmt::Formatter<'_>,
108        indentation: usize,
109    ) -> fmt::Result {
110        let indent = "    ".repeat(indentation);
111        writeln!(f, "switch ({}) {{", self.val())?;
112        for e in self.cases().iter() {
113            writeln!(f, "{}    case {}:", indent, e.condition())?;
114            e.body().display(f, indentation + 2)?;
115        }
116
117        if let Some(ref default) = self.default {
118            writeln!(f, "{}    default:", indent)?;
119            default.display(f, indentation + 2)?;
120        }
121        write!(f, "{}}}", indent)
122    }
123}
124
125impl Executable for Switch {
126    fn run(&self, context: &mut Context) -> JsResult<JsValue> {
127        let val = self.val().run(context)?;
128        let mut result = JsValue::null();
129        let mut matched = false;
130        context
131            .executor()
132            .set_current_state(InterpreterState::Executing);
133
134        // If a case block does not end with a break statement then subsequent cases will be run without
135        // checking their conditions until a break is encountered.
136        let mut fall_through: bool = false;
137
138        for case in self.cases().iter() {
139            let cond = case.condition();
140            let block = case.body();
141            if fall_through || val.strict_equals(&cond.run(context)?) {
142                matched = true;
143                let result = block.run(context)?;
144                match context.executor().get_current_state() {
145                    InterpreterState::Return => {
146                        // Early return.
147                        return Ok(result);
148                    }
149                    InterpreterState::Break(_label) => {
150                        // TODO, break to a label.
151                        // Break statement encountered so therefore end switch statement.
152                        context
153                            .executor()
154                            .set_current_state(InterpreterState::Executing);
155                        break;
156                    }
157                    InterpreterState::Continue(_label) => {
158                        // TODO, continue to a label.
159                        break;
160                    }
161                    InterpreterState::Executing => {
162                        // Continuing execution / falling through to next case statement(s).
163                        fall_through = true;
164                    }
165                }
166            }
167        }
168
169        if !matched {
170            if let Some(default) = self.default() {
171                context
172                    .executor()
173                    .set_current_state(InterpreterState::Executing);
174                for (i, item) in default.iter().enumerate() {
175                    let val = item.run(context)?;
176                    match context.executor().get_current_state() {
177                        InterpreterState::Return => {
178                            // Early return.
179                            result = val;
180                            break;
181                        }
182                        InterpreterState::Break(_label) => {
183                            // TODO, break to a label.
184
185                            // Early break.
186                            break;
187                        }
188                        _ => {
189                            // Continue execution
190                        }
191                    }
192                    if i == default.len() - 1 {
193                        result = val;
194                    }
195                }
196            }
197        }
198
199        Ok(result)
200    }
201}
202
203impl fmt::Display for Switch {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        self.display(f, 0)
206    }
207}
208
209impl From<Switch> for Node {
210    fn from(switch: Switch) -> Self {
211        Self::Switch(switch)
212    }
213}