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