boa_engine 0.17.0

Boa is a Javascript lexer, parser and compiler written in Rust. Currently, it has support for some of the language.
Documentation
use crate::{
    bytecompiler::{Access, ByteCompiler},
    environments::BindingLocatorError,
    vm::{BindingOpcode, Opcode},
};
use boa_ast::expression::{
    access::{PropertyAccess, PropertyAccessField},
    operator::{assign::AssignOp, Assign},
};

impl ByteCompiler<'_, '_> {
    pub(crate) fn compile_assign(&mut self, assign: &Assign, use_expr: bool) {
        if assign.op() == AssignOp::Assign {
            match Access::from_assign_target(assign.lhs()) {
                Ok(access) => self.access_set(access, use_expr, |compiler, _| {
                    compiler.compile_expr(assign.rhs(), true);
                }),
                Err(pattern) => {
                    self.compile_expr(assign.rhs(), true);
                    if use_expr {
                        self.emit_opcode(Opcode::Dup);
                    }
                    self.compile_declaration_pattern(pattern, BindingOpcode::SetName);
                }
            }
        } else {
            let access = Access::from_assign_target(assign.lhs())
                .expect("patterns should throw early errors on complex assignment operators");

            let opcode = match assign.op() {
                AssignOp::Assign => unreachable!(),
                AssignOp::Add => Opcode::Add,
                AssignOp::Sub => Opcode::Sub,
                AssignOp::Mul => Opcode::Mul,
                AssignOp::Div => Opcode::Div,
                AssignOp::Mod => Opcode::Mod,
                AssignOp::Exp => Opcode::Pow,
                AssignOp::And => Opcode::BitAnd,
                AssignOp::Or => Opcode::BitOr,
                AssignOp::Xor => Opcode::BitXor,
                AssignOp::Shl => Opcode::ShiftLeft,
                AssignOp::Shr => Opcode::ShiftRight,
                AssignOp::Ushr => Opcode::UnsignedShiftRight,
                AssignOp::BoolAnd => Opcode::LogicalAnd,
                AssignOp::BoolOr => Opcode::LogicalOr,
                AssignOp::Coalesce => Opcode::Coalesce,
            };

            let short_circuit = matches!(
                assign.op(),
                AssignOp::BoolAnd | AssignOp::BoolOr | AssignOp::Coalesce
            );
            let mut pop_count = 0;
            let mut early_exit = None;

            match access {
                Access::Variable { name } => {
                    let binding = self.get_binding_value(name);
                    let index = self.get_or_insert_binding(binding);
                    let lex = self.current_environment.borrow().is_lex_binding(name);

                    if lex {
                        self.emit(Opcode::GetName, &[index]);
                    } else {
                        self.emit(Opcode::GetNameAndLocator, &[index]);
                    }

                    if short_circuit {
                        early_exit = Some(self.emit_opcode_with_operand(opcode));
                        self.compile_expr(assign.rhs(), true);
                    } else {
                        self.compile_expr(assign.rhs(), true);
                        self.emit_opcode(opcode);
                    }
                    if use_expr {
                        self.emit_opcode(Opcode::Dup);
                    }
                    if lex {
                        match self.set_mutable_binding(name) {
                            Ok(binding) => {
                                let index = self.get_or_insert_binding(binding);
                                self.emit(Opcode::SetName, &[index]);
                            }
                            Err(BindingLocatorError::MutateImmutable) => {
                                let index = self.get_or_insert_name(name);
                                self.emit(Opcode::ThrowMutateImmutable, &[index]);
                            }
                            Err(BindingLocatorError::Silent) => {
                                self.emit(Opcode::Pop, &[]);
                            }
                        }
                    } else {
                        self.emit_opcode(Opcode::SetNameByLocator);
                    }
                }
                Access::Property { access } => match access {
                    PropertyAccess::Simple(access) => match access.field() {
                        PropertyAccessField::Const(name) => {
                            let index = self.get_or_insert_name((*name).into());
                            self.compile_expr(access.target(), true);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::Dup);

                            self.emit(Opcode::GetPropertyByName, &[index]);
                            if short_circuit {
                                pop_count = 2;
                                early_exit = Some(self.emit_opcode_with_operand(opcode));
                                self.compile_expr(assign.rhs(), true);
                            } else {
                                self.compile_expr(assign.rhs(), true);
                                self.emit_opcode(opcode);
                            }

                            self.emit(Opcode::SetPropertyByName, &[index]);
                            if !use_expr {
                                self.emit_opcode(Opcode::Pop);
                            }
                        }
                        PropertyAccessField::Expr(expr) => {
                            self.compile_expr(access.target(), true);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::Dup);
                            self.compile_expr(expr, true);

                            self.emit_opcode(Opcode::GetPropertyByValuePush);
                            if short_circuit {
                                pop_count = 3;
                                early_exit = Some(self.emit_opcode_with_operand(opcode));
                                self.compile_expr(assign.rhs(), true);
                            } else {
                                self.compile_expr(assign.rhs(), true);
                                self.emit_opcode(opcode);
                            }

                            self.emit_opcode(Opcode::SetPropertyByValue);
                            if !use_expr {
                                self.emit_opcode(Opcode::Pop);
                            }
                        }
                    },
                    PropertyAccess::Private(access) => {
                        let index = self.get_or_insert_private_name(access.field());
                        self.compile_expr(access.target(), true);
                        self.emit_opcode(Opcode::Dup);

                        self.emit(Opcode::GetPrivateField, &[index]);
                        if short_circuit {
                            pop_count = 1;
                            early_exit = Some(self.emit_opcode_with_operand(opcode));
                            self.compile_expr(assign.rhs(), true);
                        } else {
                            self.compile_expr(assign.rhs(), true);
                            self.emit_opcode(opcode);
                        }

                        self.emit(Opcode::SetPrivateField, &[index]);
                        if !use_expr {
                            self.emit_opcode(Opcode::Pop);
                        }
                    }
                    PropertyAccess::Super(access) => match access.field() {
                        PropertyAccessField::Const(name) => {
                            let index = self.get_or_insert_name((*name).into());
                            self.emit_opcode(Opcode::Super);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::This);
                            self.emit_opcode(Opcode::Swap);
                            self.emit_opcode(Opcode::This);

                            self.emit(Opcode::GetPropertyByName, &[index]);
                            if short_circuit {
                                pop_count = 2;
                                early_exit = Some(self.emit_opcode_with_operand(opcode));
                                self.compile_expr(assign.rhs(), true);
                            } else {
                                self.compile_expr(assign.rhs(), true);
                                self.emit_opcode(opcode);
                            }

                            self.emit(Opcode::SetPropertyByName, &[index]);
                            if !use_expr {
                                self.emit_opcode(Opcode::Pop);
                            }
                        }
                        PropertyAccessField::Expr(expr) => {
                            self.emit_opcode(Opcode::Super);
                            self.emit_opcode(Opcode::Dup);
                            self.emit_opcode(Opcode::This);
                            self.compile_expr(expr, true);

                            self.emit_opcode(Opcode::GetPropertyByValuePush);
                            if short_circuit {
                                pop_count = 2;
                                early_exit = Some(self.emit_opcode_with_operand(opcode));
                                self.compile_expr(assign.rhs(), true);
                            } else {
                                self.compile_expr(assign.rhs(), true);
                                self.emit_opcode(opcode);
                            }

                            self.emit_opcode(Opcode::This);
                            self.emit_opcode(Opcode::RotateRight);
                            self.emit_u8(2);

                            self.emit_opcode(Opcode::SetPropertyByValue);
                            if !use_expr {
                                self.emit_opcode(Opcode::Pop);
                            }
                        }
                    },
                },
                Access::This => unreachable!(),
            }

            if let Some(early_exit) = early_exit {
                if pop_count == 0 {
                    self.patch_jump(early_exit);
                } else {
                    let exit = self.emit_opcode_with_operand(Opcode::Jump);
                    self.patch_jump(early_exit);
                    for _ in 0..pop_count {
                        self.emit_opcode(Opcode::Swap);
                        self.emit_opcode(Opcode::Pop);
                    }
                    self.patch_jump(exit);
                }
            }
        }
    }
}