boa/syntax/ast/node/operator/bin_op/
mod.rs

1use crate::{
2    exec::Executable,
3    gc::{Finalize, Trace},
4    symbol::WellKnownSymbols,
5    syntax::ast::{
6        node::Node,
7        op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp},
8    },
9    Context, JsResult, JsValue,
10};
11use std::fmt;
12
13#[cfg(feature = "deser")]
14use serde::{Deserialize, Serialize};
15
16/// Binary operators requires two operands, one before the operator and one after the operator.
17///
18/// More information:
19///  - [MDN documentation][mdn]
20///
21/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators
22#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
23#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
24pub struct BinOp {
25    op: op::BinOp,
26    lhs: Box<Node>,
27    rhs: Box<Node>,
28}
29
30impl BinOp {
31    /// Creates a `BinOp` AST node.
32    pub(in crate::syntax) fn new<O, L, R>(op: O, lhs: L, rhs: R) -> Self
33    where
34        O: Into<op::BinOp>,
35        L: Into<Node>,
36        R: Into<Node>,
37    {
38        Self {
39            op: op.into(),
40            lhs: Box::new(lhs.into()),
41            rhs: Box::new(rhs.into()),
42        }
43    }
44
45    /// Gets the binary operation of the node.
46    pub fn op(&self) -> op::BinOp {
47        self.op
48    }
49
50    /// Gets the left hand side of the binary operation.
51    pub fn lhs(&self) -> &Node {
52        &self.lhs
53    }
54
55    /// Gets the right hand side of the binary operation.
56    pub fn rhs(&self) -> &Node {
57        &self.rhs
58    }
59
60    /// Runs the assignment operators.
61    fn run_assign(op: AssignOp, x: JsValue, y: &Node, context: &mut Context) -> JsResult<JsValue> {
62        match op {
63            AssignOp::Add => x.add(&y.run(context)?, context),
64            AssignOp::Sub => x.sub(&y.run(context)?, context),
65            AssignOp::Mul => x.mul(&y.run(context)?, context),
66            AssignOp::Exp => x.pow(&y.run(context)?, context),
67            AssignOp::Div => x.div(&y.run(context)?, context),
68            AssignOp::Mod => x.rem(&y.run(context)?, context),
69            AssignOp::And => x.bitand(&y.run(context)?, context),
70            AssignOp::Or => x.bitor(&y.run(context)?, context),
71            AssignOp::Xor => x.bitxor(&y.run(context)?, context),
72            AssignOp::Shl => x.shl(&y.run(context)?, context),
73            AssignOp::Shr => x.shr(&y.run(context)?, context),
74            AssignOp::Ushr => x.ushr(&y.run(context)?, context),
75            AssignOp::BoolAnd => {
76                if x.to_boolean() {
77                    Ok(y.run(context)?)
78                } else {
79                    Ok(x)
80                }
81            }
82            AssignOp::BoolOr => {
83                if x.to_boolean() {
84                    Ok(x)
85                } else {
86                    Ok(y.run(context)?)
87                }
88            }
89            AssignOp::Coalesce => {
90                if x.is_null_or_undefined() {
91                    Ok(y.run(context)?)
92                } else {
93                    Ok(x)
94                }
95            }
96        }
97    }
98}
99
100impl Executable for BinOp {
101    fn run(&self, context: &mut Context) -> JsResult<JsValue> {
102        match self.op() {
103            op::BinOp::Num(op) => {
104                let x = self.lhs().run(context)?;
105                let y = self.rhs().run(context)?;
106                match op {
107                    NumOp::Add => x.add(&y, context),
108                    NumOp::Sub => x.sub(&y, context),
109                    NumOp::Mul => x.mul(&y, context),
110                    NumOp::Exp => x.pow(&y, context),
111                    NumOp::Div => x.div(&y, context),
112                    NumOp::Mod => x.rem(&y, context),
113                }
114            }
115            op::BinOp::Bit(op) => {
116                let x = self.lhs().run(context)?;
117                let y = self.rhs().run(context)?;
118                match op {
119                    BitOp::And => x.bitand(&y, context),
120                    BitOp::Or => x.bitor(&y, context),
121                    BitOp::Xor => x.bitxor(&y, context),
122                    BitOp::Shl => x.shl(&y, context),
123                    BitOp::Shr => x.shr(&y, context),
124                    BitOp::UShr => x.ushr(&y, context),
125                }
126            }
127            op::BinOp::Comp(op) => {
128                let x = self.lhs().run(context)?;
129                let y = self.rhs().run(context)?;
130                Ok(JsValue::new(match op {
131                    CompOp::Equal => x.equals(&y, context)?,
132                    CompOp::NotEqual => !x.equals(&y, context)?,
133                    CompOp::StrictEqual => x.strict_equals(&y),
134                    CompOp::StrictNotEqual => !x.strict_equals(&y),
135                    CompOp::GreaterThan => x.gt(&y, context)?,
136                    CompOp::GreaterThanOrEqual => x.ge(&y, context)?,
137                    CompOp::LessThan => x.lt(&y, context)?,
138                    CompOp::LessThanOrEqual => x.le(&y, context)?,
139                    CompOp::In => {
140                        if !y.is_object() {
141                            return context.throw_type_error(format!(
142                                "right-hand side of 'in' should be an object, got {}",
143                                y.type_of()
144                            ));
145                        }
146                        let key = x.to_property_key(context)?;
147                        context.has_property(&y, &key)?
148                    }
149                    CompOp::InstanceOf => {
150                        if let Some(object) = y.as_object() {
151                            let key = WellKnownSymbols::has_instance();
152
153                            match object.get_method(context, key)? {
154                                Some(instance_of_handler) => {
155                                    instance_of_handler.call(&y, &[x], context)?.to_boolean()
156                                }
157                                None if object.is_callable() => {
158                                    object.ordinary_has_instance(context, &x)?
159                                }
160                                None => {
161                                    return context.throw_type_error(
162                                        "right-hand side of 'instanceof' is not callable",
163                                    );
164                                }
165                            }
166                        } else {
167                            return context.throw_type_error(format!(
168                                "right-hand side of 'instanceof' should be an object, got {}",
169                                y.type_of()
170                            ));
171                        }
172                    }
173                }))
174            }
175            op::BinOp::Log(op) => Ok(match op {
176                LogOp::And => {
177                    let left = self.lhs().run(context)?;
178                    if !left.to_boolean() {
179                        left
180                    } else {
181                        self.rhs().run(context)?
182                    }
183                }
184                LogOp::Or => {
185                    let left = self.lhs().run(context)?;
186                    if left.to_boolean() {
187                        left
188                    } else {
189                        self.rhs().run(context)?
190                    }
191                }
192                LogOp::Coalesce => {
193                    let left = self.lhs.run(context)?;
194                    if left.is_null_or_undefined() {
195                        self.rhs().run(context)?
196                    } else {
197                        left
198                    }
199                }
200            }),
201            op::BinOp::Assign(op) => match self.lhs() {
202                Node::Identifier(ref name) => {
203                    let v_a = context.get_binding_value(name.as_ref())?;
204
205                    let value = Self::run_assign(op, v_a, self.rhs(), context)?;
206                    context.set_mutable_binding(name.as_ref(), value.clone(), context.strict())?;
207                    Ok(value)
208                }
209                Node::GetConstField(ref get_const_field) => {
210                    let v_r_a = get_const_field.obj().run(context)?;
211                    let v_a = v_r_a.get_field(get_const_field.field(), context)?;
212                    let value = Self::run_assign(op, v_a, self.rhs(), context)?;
213                    v_r_a.set_field(get_const_field.field(), value.clone(), false, context)?;
214                    Ok(value)
215                }
216                _ => Ok(JsValue::undefined()),
217            },
218            op::BinOp::Comma => {
219                self.lhs().run(context)?;
220                Ok(self.rhs().run(context)?)
221            }
222        }
223    }
224}
225
226impl fmt::Display for BinOp {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(f, "{} {} {}", self.lhs, self.op, self.rhs)
229    }
230}
231
232impl From<BinOp> for Node {
233    fn from(op: BinOp) -> Self {
234        Self::BinOp(op)
235    }
236}