use crate::{
builtins::function::ThisMode,
bytecompiler::{ByteCompiler, FunctionKind},
syntax::ast::node::{Declaration, FormalParameterList, StatementList},
vm::{BindingOpcode, CodeBlock, Opcode},
Context, JsResult,
};
use boa_gc::Gc;
use boa_interner::Sym;
use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Copy)]
pub(crate) struct FunctionCompiler {
name: Sym,
generator: bool,
r#async: bool,
strict: bool,
kind: FunctionKind,
}
impl FunctionCompiler {
#[inline]
pub(crate) fn new() -> Self {
Self {
name: Sym::EMPTY_STRING,
generator: false,
r#async: false,
strict: false,
kind: FunctionKind::Declaration,
}
}
#[inline]
pub(crate) fn name<N>(mut self, name: N) -> Self
where
N: Into<Option<Sym>>,
{
let name = name.into();
if let Some(name) = name {
self.name = name;
}
self
}
#[inline]
pub(crate) fn generator(mut self, generator: bool) -> Self {
self.generator = generator;
self
}
#[inline]
pub(crate) fn r#async(mut self, r#async: bool) -> Self {
self.r#async = r#async;
self
}
#[inline]
pub(crate) fn strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
}
#[inline]
pub(crate) fn kind(mut self, kind: FunctionKind) -> Self {
self.kind = kind;
self
}
pub(crate) fn compile(
mut self,
parameters: &FormalParameterList,
body: &StatementList,
context: &mut Context,
) -> JsResult<Gc<CodeBlock>> {
self.strict = self.strict || body.strict();
let length = parameters.length();
let mut code = CodeBlock::new(self.name, length, self.strict);
if self.kind == FunctionKind::Arrow {
code.this_mode = ThisMode::Lexical;
}
let mut compiler = ByteCompiler {
code_block: code,
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
in_async_generator: self.generator && self.r#async,
context,
};
compiler.context.push_compile_time_environment(true);
if !(self.kind == FunctionKind::Arrow) && !parameters.has_arguments() {
compiler
.context
.create_mutable_binding(Sym::ARGUMENTS, false);
compiler.code_block.arguments_binding = Some(
compiler
.context
.initialize_mutable_binding(Sym::ARGUMENTS, false),
);
}
for parameter in parameters.parameters.iter() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
compiler.context.create_mutable_binding(ident.sym(), false);
if let Some(init) = parameter.declaration().init() {
let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?;
compiler.patch_jump(skip);
}
compiler.emit_binding(BindingOpcode::InitArg, ident.sym());
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
compiler.context.create_mutable_binding(ident, false);
}
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?;
}
}
}
if !parameters.has_rest_parameter() {
compiler.emit_opcode(Opcode::RestParameterPop);
}
let env_label = if parameters.has_expressions() {
compiler.code_block.num_bindings = compiler.context.get_binding_number();
compiler.context.push_compile_time_environment(true);
compiler.code_block.function_environment_push_location =
compiler.next_opcode_location();
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else {
None
};
if self.generator {
compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit_opcode(Opcode::Yield);
}
compiler.create_declarations(body.items())?;
compiler.compile_statement_list(body.items(), false)?;
if let Some(env_label) = env_label {
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
let index_compile_environment = compiler.push_compile_environment(compile_environment);
compiler.patch_jump_with_target(env_label.0, num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32);
let (_, compile_environment) = compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
} else {
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
compiler
.code_block
.compile_environments
.push(compile_environment);
compiler.code_block.num_bindings = num_bindings;
}
compiler.code_block.params = parameters.clone();
compiler.emit(Opcode::PushUndefined, &[]);
compiler.emit(Opcode::Return, &[]);
Ok(Gc::new(compiler.finish()))
}
}