mirror of https://github.com/boa-dev/boa.git
Browse Source
* WIP: 76d7eceed6 Implement dynamic imports * Expand `ActiveRunnable` to missing places * Fix memory leak * Fix docs * Parse `import` as call expression * Fix regressions * Fix copypasted doc * clippy fix * Adjust ignored features * Migrate away from `top_level_*` operations * Fix more module tests * Fix doc linkpull/2958/head
José Julián Espina
1 year ago
committed by
GitHub
89 changed files with 2192 additions and 982 deletions
@ -0,0 +1,111 @@ |
|||||||
|
use std::ops::ControlFlow; |
||||||
|
|
||||||
|
use boa_interner::ToIndentedString; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
visitor::{VisitWith, Visitor, VisitorMut}, |
||||||
|
ModuleItemList, StatementList, |
||||||
|
}; |
||||||
|
|
||||||
|
/// A Script source.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-scripts
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] |
||||||
|
#[derive(Clone, Debug, Default, PartialEq)] |
||||||
|
pub struct Script { |
||||||
|
statements: StatementList, |
||||||
|
} |
||||||
|
|
||||||
|
impl Script { |
||||||
|
/// Creates a new `ScriptNode`.
|
||||||
|
#[must_use] |
||||||
|
pub const fn new(statements: StatementList) -> Self { |
||||||
|
Self { statements } |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the list of statements of this `ScriptNode`.
|
||||||
|
#[must_use] |
||||||
|
pub const fn statements(&self) -> &StatementList { |
||||||
|
&self.statements |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets a mutable reference to the list of statements of this `ScriptNode`.
|
||||||
|
pub fn statements_mut(&mut self) -> &mut StatementList { |
||||||
|
&mut self.statements |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the strict mode.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub const fn strict(&self) -> bool { |
||||||
|
self.statements.strict() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for Script { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
self.statements.visit_with(visitor) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
self.statements.visit_with_mut(visitor) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToIndentedString for Script { |
||||||
|
fn to_indented_string(&self, interner: &boa_interner::Interner, indentation: usize) -> String { |
||||||
|
self.statements.to_indented_string(interner, indentation) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A Module source.
|
||||||
|
///
|
||||||
|
/// More information:
|
||||||
|
/// - [ECMAScript reference][spec]
|
||||||
|
///
|
||||||
|
/// [spec]: https://tc39.es/ecma262/#sec-modules
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
||||||
|
#[derive(Clone, Debug, Default, PartialEq)] |
||||||
|
pub struct Module { |
||||||
|
items: ModuleItemList, |
||||||
|
} |
||||||
|
|
||||||
|
impl Module { |
||||||
|
/// Creates a new `ModuleNode`.
|
||||||
|
#[must_use] |
||||||
|
pub const fn new(items: ModuleItemList) -> Self { |
||||||
|
Self { items } |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the list of itemos of this `ModuleNode`.
|
||||||
|
#[must_use] |
||||||
|
pub const fn items(&self) -> &ModuleItemList { |
||||||
|
&self.items |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl VisitWith for Module { |
||||||
|
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: Visitor<'a>, |
||||||
|
{ |
||||||
|
self.items.visit_with(visitor) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy> |
||||||
|
where |
||||||
|
V: VisitorMut<'a>, |
||||||
|
{ |
||||||
|
self.items.visit_with_mut(visitor) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,163 @@ |
|||||||
|
//! 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.
|
||||||
|
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 active_function = context.vm.active_function.take(); |
||||||
|
let stack = std::mem::take(&mut context.vm.stack); |
||||||
|
let old_active = context |
||||||
|
.vm |
||||||
|
.active_runnable |
||||||
|
.replace(ActiveRunnable::Script(self.clone())); |
||||||
|
context.vm.push_frame(CallFrame::new(codeblock)); |
||||||
|
|
||||||
|
// TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
|
||||||
|
|
||||||
|
self.realm().resize_global_env(); |
||||||
|
let record = context.run(); |
||||||
|
context.vm.pop_frame(); |
||||||
|
|
||||||
|
context.vm.stack = stack; |
||||||
|
context.vm.active_function = active_function; |
||||||
|
context.vm.active_runnable = old_active; |
||||||
|
context.enter_realm(old_realm); |
||||||
|
|
||||||
|
context.clear_kept_objects(); |
||||||
|
|
||||||
|
record.consume() |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue