mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.0 KiB
159 lines
5.0 KiB
//! Boa's implementation of ECMAScript's Scripts. |
|
//! |
|
//! This module contains the [`Script`] type, which represents a [**Script Record**][script]. |
|
//! |
|
//! More information: |
|
//! - [ECMAScript reference][spec] |
|
//! |
|
//! [spec]: https://tc39.es/ecma262/#sec-scripts |
|
//! [script]: https://tc39.es/ecma262/#sec-script-records |
|
|
|
use std::io::Read; |
|
|
|
use boa_gc::{Finalize, Gc, GcRefCell, Trace}; |
|
use boa_interner::Sym; |
|
use boa_parser::{Parser, Source}; |
|
use boa_profiler::Profiler; |
|
use rustc_hash::FxHashMap; |
|
|
|
use crate::{ |
|
bytecompiler::ByteCompiler, |
|
realm::Realm, |
|
vm::{ActiveRunnable, CallFrame, CodeBlock}, |
|
Context, JsResult, JsString, JsValue, Module, |
|
}; |
|
|
|
/// ECMAScript's [**Script Record**][spec]. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-script-records |
|
#[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("code", &self.inner.source) |
|
.field("loaded_modules", &self.inner.loaded_modules) |
|
.field("host_defined", &self.inner.host_defined) |
|
.finish() |
|
} |
|
} |
|
|
|
#[derive(Trace, Finalize)] |
|
struct Inner { |
|
realm: Realm, |
|
#[unsafe_ignore_trace] |
|
source: boa_ast::Script, |
|
codeblock: GcRefCell<Option<Gc<CodeBlock>>>, |
|
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>, |
|
host_defined: (), |
|
} |
|
|
|
impl Script { |
|
/// Gets the realm of this script. |
|
#[must_use] |
|
pub fn realm(&self) -> &Realm { |
|
&self.inner.realm |
|
} |
|
|
|
/// Gets the loaded modules of this script. |
|
pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<JsString, Module>> { |
|
&self.inner.loaded_modules |
|
} |
|
|
|
/// Abstract operation [`ParseScript ( sourceText, realm, hostDefined )`][spec]. |
|
/// |
|
/// Parses the provided `src` as an ECMAScript script, returning an error if parsing fails. |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-parse-script |
|
pub fn parse<R: Read>( |
|
src: Source<'_, R>, |
|
realm: Option<Realm>, |
|
context: &mut Context<'_>, |
|
) -> JsResult<Self> { |
|
let _timer = Profiler::global().start_event("Script parsing", "Main"); |
|
let mut parser = Parser::new(src); |
|
parser.set_identifier(context.next_parser_identifier()); |
|
if context.is_strict() { |
|
parser.set_strict(); |
|
} |
|
let mut code = parser.parse_script(context.interner_mut())?; |
|
if !context.optimizer_options().is_empty() { |
|
context.optimize_statement_list(code.statements_mut()); |
|
} |
|
|
|
Ok(Self { |
|
inner: Gc::new(Inner { |
|
realm: realm.unwrap_or_else(|| context.realm().clone()), |
|
source: code, |
|
codeblock: GcRefCell::default(), |
|
loaded_modules: GcRefCell::default(), |
|
host_defined: (), |
|
}), |
|
}) |
|
} |
|
|
|
/// Compiles the codeblock of this script. |
|
/// |
|
/// This is a no-op if this has been called previously. |
|
pub fn codeblock(&self, context: &mut Context<'_>) -> JsResult<Gc<CodeBlock>> { |
|
let mut codeblock = self.inner.codeblock.borrow_mut(); |
|
|
|
if let Some(codeblock) = &*codeblock { |
|
return Ok(codeblock.clone()); |
|
}; |
|
|
|
let _timer = Profiler::global().start_event("Script compilation", "Main"); |
|
|
|
let mut compiler = ByteCompiler::new( |
|
Sym::MAIN, |
|
self.inner.source.strict(), |
|
false, |
|
self.inner.realm.environment().compile_env(), |
|
context, |
|
); |
|
// TODO: move to `Script::evaluate` to make this operation infallible. |
|
compiler.global_declaration_instantiation(&self.inner.source)?; |
|
compiler.compile_statement_list(self.inner.source.statements(), true, false); |
|
|
|
let cb = Gc::new(compiler.finish()); |
|
|
|
*codeblock = Some(cb.clone()); |
|
|
|
Ok(cb) |
|
} |
|
|
|
/// Evaluates this script and returns its result. |
|
/// |
|
/// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] |
|
/// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. |
|
/// |
|
/// [`JobQueue::run_jobs`]: crate::job::JobQueue::run_jobs |
|
pub fn evaluate(&self, context: &mut Context<'_>) -> JsResult<JsValue> { |
|
let _timer = Profiler::global().start_event("Execution", "Main"); |
|
|
|
let codeblock = self.codeblock(context)?; |
|
|
|
let old_realm = context.enter_realm(self.inner.realm.clone()); |
|
let env_fp = context.vm.environments.len() as u32; |
|
context.vm.push_frame( |
|
CallFrame::new(codeblock, Some(ActiveRunnable::Script(self.clone())), None) |
|
.with_env_fp(env_fp), |
|
); |
|
|
|
// TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation |
|
|
|
self.realm().resize_global_env(); |
|
let record = context.run(); |
|
context.vm.pop_frame(); |
|
|
|
context.enter_realm(old_realm); |
|
|
|
context.clear_kept_objects(); |
|
|
|
record.consume() |
|
} |
|
}
|
|
|