use std::path::{Path, PathBuf};
use rustc_hash::FxHashMap;
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_parser::{Parser, Source, source::ReadChar};
use crate::{
Context, HostDefined, JsResult, JsString, JsValue, Module, SpannedSourceText,
bytecompiler::{ByteCompiler, global_declaration_instantiation_context},
environments::EnvironmentStack,
js_string,
realm::Realm,
spanned_source_text::SourceText,
vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
};
#[derive(Clone, Trace, Finalize)]
pub struct Script {
inner: Gc<Inner>,
}
impl std::fmt::Debug for Script {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Script")
.field("realm", &self.inner.realm.addr())
.field("phase", &self.inner.phase.borrow())
.field("loaded_modules", &self.inner.loaded_modules)
.finish()
}
}
#[derive(Trace, Debug, Finalize)]
enum ScriptPhase {
Ast(#[unsafe_ignore_trace] boa_ast::Script),
Codeblock(Gc<CodeBlock>),
}
#[derive(Trace, Finalize)]
struct Inner {
realm: Realm,
phase: GcRefCell<ScriptPhase>,
source_text: SourceText,
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_defined: HostDefined,
path: Option<PathBuf>,
}
impl Script {
#[must_use]
pub fn realm(&self) -> &Realm {
&self.inner.realm
}
#[must_use]
pub fn host_defined(&self) -> &HostDefined {
&self.inner.host_defined
}
pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<JsString, Module>> {
&self.inner.loaded_modules
}
pub fn parse<R: ReadChar>(
src: Source<'_, R>,
realm: Option<Realm>,
context: &mut Context,
) -> JsResult<Self> {
let path = src.path().map(Path::to_path_buf);
let mut parser = Parser::new(src);
parser.set_identifier(context.next_parser_identifier());
if context.is_strict() {
parser.set_strict();
}
let scope = context.realm().scope().clone();
let (mut code, source) = parser.parse_script_with_source(&scope, context.interner_mut())?;
if !context.optimizer_options().is_empty() {
context.optimize_statement_list(code.statements_mut());
}
let source_text = SourceText::new(source);
Ok(Self {
inner: Gc::new(Inner {
realm: realm.unwrap_or_else(|| context.realm().clone()),
phase: GcRefCell::new(ScriptPhase::Ast(code)),
source_text,
loaded_modules: GcRefCell::default(),
host_defined: HostDefined::default(),
path,
}),
})
}
pub fn codeblock(&self, context: &mut Context) -> JsResult<Gc<CodeBlock>> {
let cb = {
let phase = self.inner.phase.borrow();
let source = match &*phase {
ScriptPhase::Codeblock(codeblock) => return Ok(codeblock.clone()),
ScriptPhase::Ast(source) => source,
};
let mut annex_b_function_names = Vec::new();
global_declaration_instantiation_context(
&mut annex_b_function_names,
source,
self.inner.realm.scope(),
context,
)?;
let spanned_source_text = SpannedSourceText::new_source_only(self.get_source());
let mut compiler = ByteCompiler::new(
js_string!("<main>"),
source.strict(),
false,
self.inner.realm.scope().clone(),
self.inner.realm.scope().clone(),
false,
false,
context.interner_mut(),
false,
spanned_source_text,
self.path().map(Path::to_owned).into(),
);
#[cfg(feature = "annex-b")]
{
compiler.annex_b_function_names = annex_b_function_names;
}
compiler.global_declaration_instantiation(source);
compiler.compile_statement_list(source.statements(), true, false);
Gc::new(compiler.finish())
};
*self.inner.phase.borrow_mut() = ScriptPhase::Codeblock(cb.clone());
Ok(cb)
}
pub fn evaluate(&self, context: &mut Context) -> JsResult<JsValue> {
self.prepare_run(context)?;
let record = context.run();
context.vm.pop_frame();
record.consume()
}
#[allow(clippy::future_not_send)]
pub async fn evaluate_async(&self, context: &mut Context) -> JsResult<JsValue> {
self.evaluate_async_with_budget(context, 256).await
}
#[allow(clippy::future_not_send)]
pub async fn evaluate_async_with_budget(
&self,
context: &mut Context,
budget: u32,
) -> JsResult<JsValue> {
self.prepare_run(context)?;
let record = context.run_async_with_budget(budget).await;
context.vm.pop_frame();
record.consume()
}
fn prepare_run(&self, context: &mut Context) -> JsResult<()> {
let codeblock = self.codeblock(context)?;
let global_env = EnvironmentStack::new();
context.vm.push_frame_with_stack(
CallFrame::new(
codeblock.clone(),
Some(ActiveRunnable::Script(self.clone())),
global_env,
self.inner.realm.clone(),
)
.with_env_fp(0)
.with_flags(CallFrameFlags::EXIT_EARLY),
JsValue::undefined(),
JsValue::null(),
);
self.realm().resize_global_env();
context
.global_declaration_instantiation(&codeblock)
.inspect_err(|_| {
context.vm.pop_frame();
})?;
Ok(())
}
pub(super) fn path(&self) -> Option<&Path> {
self.inner.path.as_deref()
}
pub(super) fn get_source(&self) -> SourceText {
self.inner.source_text.clone()
}
}