|
|
|
use crate::{
|
|
|
|
exec::Executable,
|
|
|
|
gc::{Finalize, Trace},
|
|
|
|
syntax::ast::{node::Node, op},
|
|
|
|
Context, Result, Value,
|
|
|
|
};
|
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
#[cfg(feature = "deser")]
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
#[cfg(feature = "vm")]
|
|
|
|
use crate::{
|
|
|
|
profiler::BoaProfiler,
|
|
|
|
vm::{compilation::CodeGen, Compiler, Instruction},
|
|
|
|
};
|
|
|
|
|
|
|
|
/// A unary operation is an operation with only one operand.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators
|
|
|
|
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
|
|
|
|
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
|
|
|
|
pub struct UnaryOp {
|
|
|
|
op: op::UnaryOp,
|
|
|
|
target: Box<Node>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UnaryOp {
|
|
|
|
/// Creates a new `UnaryOp` AST node.
|
|
|
|
pub(in crate::syntax) fn new<V>(op: op::UnaryOp, target: V) -> Self
|
|
|
|
where
|
|
|
|
V: Into<Node>,
|
|
|
|
{
|
|
|
|
Self {
|
|
|
|
op,
|
|
|
|
target: Box::new(target.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the unary operation of the node.
|
|
|
|
pub fn op(&self) -> op::UnaryOp {
|
|
|
|
self.op
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the target of this unary operator.
|
|
|
|
pub fn target(&self) -> &Node {
|
|
|
|
self.target.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Executable for UnaryOp {
|
|
|
|
fn run(&self, context: &mut Context) -> Result<Value> {
|
|
|
|
let x = self.target().run(context)?;
|
|
|
|
|
|
|
|
Ok(match self.op() {
|
|
|
|
op::UnaryOp::Minus => x.neg(context)?,
|
|
|
|
op::UnaryOp::Plus => Value::from(x.to_number(context)?),
|
|
|
|
op::UnaryOp::IncrementPost => {
|
|
|
|
let ret = x.clone();
|
|
|
|
let result = x.to_number(context)? + 1.0;
|
|
|
|
context.set_value(self.target(), result.into())?;
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
op::UnaryOp::IncrementPre => {
|
|
|
|
let result = x.to_number(context)? + 1.0;
|
|
|
|
context.set_value(self.target(), result.into())?
|
|
|
|
}
|
|
|
|
op::UnaryOp::DecrementPost => {
|
|
|
|
let ret = x.clone();
|
|
|
|
let result = x.to_number(context)? - 1.0;
|
|
|
|
context.set_value(self.target(), result.into())?;
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
op::UnaryOp::DecrementPre => {
|
|
|
|
let result = x.to_number(context)? - 1.0;
|
|
|
|
context.set_value(self.target(), result.into())?
|
|
|
|
}
|
|
|
|
op::UnaryOp::Not => x.not(context)?.into(),
|
|
|
|
op::UnaryOp::Tilde => {
|
|
|
|
let num_v_a = x.to_number(context)?;
|
|
|
|
Value::from(if num_v_a.is_nan() {
|
|
|
|
-1
|
|
|
|
} else {
|
|
|
|
// TODO: this is not spec compliant.
|
|
|
|
!(num_v_a as i32)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
op::UnaryOp::Void => Value::undefined(),
|
|
|
|
op::UnaryOp::Delete => match *self.target() {
|
|
|
|
Node::GetConstField(ref get_const_field) => Value::boolean(
|
|
|
|
get_const_field
|
|
|
|
.obj()
|
|
|
|
.run(context)?
|
|
|
|
.to_object(context)?
|
|
|
|
.delete(&get_const_field.field().into()),
|
|
|
|
),
|
|
|
|
Node::GetField(ref get_field) => {
|
|
|
|
let obj = get_field.obj().run(context)?;
|
|
|
|
let field = &get_field.field().run(context)?;
|
|
|
|
let res = obj
|
|
|
|
.to_object(context)?
|
|
|
|
.delete(&field.to_property_key(context)?);
|
|
|
|
return Ok(Value::boolean(res));
|
|
|
|
}
|
|
|
|
Node::Identifier(_) => Value::boolean(false),
|
|
|
|
Node::ArrayDecl(_)
|
|
|
|
| Node::Block(_)
|
|
|
|
| Node::Const(_)
|
|
|
|
| Node::FunctionDecl(_)
|
|
|
|
| Node::FunctionExpr(_)
|
|
|
|
| Node::New(_)
|
|
|
|
| Node::Object(_)
|
|
|
|
| Node::UnaryOp(_) => Value::boolean(true),
|
|
|
|
_ => return context.throw_syntax_error(format!("wrong delete argument {}", self)),
|
|
|
|
},
|
|
|
|
op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for UnaryOp {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{}{}", self.op, self.target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<UnaryOp> for Node {
|
|
|
|
fn from(op: UnaryOp) -> Self {
|
|
|
|
Self::UnaryOp(op)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "vm")]
|
|
|
|
impl CodeGen for UnaryOp {
|
|
|
|
fn compile(&self, compiler: &mut Compiler) {
|
|
|
|
let _timer = BoaProfiler::global().start_event("UnaryOp", "codeGen");
|
|
|
|
self.target().compile(compiler);
|
|
|
|
match self.op {
|
|
|
|
op::UnaryOp::Void => compiler.add_instruction(Instruction::Void),
|
|
|
|
op::UnaryOp::Plus => compiler.add_instruction(Instruction::Pos),
|
|
|
|
op::UnaryOp::Minus => compiler.add_instruction(Instruction::Neg),
|
|
|
|
op::UnaryOp::TypeOf => compiler.add_instruction(Instruction::TypeOf),
|
|
|
|
op::UnaryOp::Not => compiler.add_instruction(Instruction::Not),
|
|
|
|
op::UnaryOp::Tilde => compiler.add_instruction(Instruction::BitNot),
|
|
|
|
op::UnaryOp::IncrementPost => {}
|
|
|
|
op::UnaryOp::IncrementPre => {}
|
|
|
|
op::UnaryOp::DecrementPost => {}
|
|
|
|
op::UnaryOp::DecrementPre => {}
|
|
|
|
op::UnaryOp::Delete => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|