From 848b2956593a0b3954284eb77eaf89e07140daf7 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 15 May 2023 12:49:45 -0600 Subject: [PATCH 01/12] WIP: 76d7eceed6 Implement dynamic imports --- boa_ast/src/expression/call.rs | 65 ++++++ boa_ast/src/expression/mod.rs | 9 +- boa_ast/src/visitor.rs | 7 +- boa_cli/src/main.rs | 9 +- boa_engine/benches/full.rs | 32 ++- boa_engine/src/builtins/eval/mod.rs | 17 +- boa_engine/src/builtins/json/mod.rs | 9 +- boa_engine/src/bytecompiler/expression/mod.rs | 7 + boa_engine/src/context/mod.rs | 78 +------ boa_engine/src/lib.rs | 4 +- boa_engine/src/module/mod.rs | 17 +- boa_engine/src/module/source.rs | 21 +- boa_engine/src/script.rs | 162 +++++++++++++ boa_engine/src/vm/code_block.rs | 1 + boa_engine/src/vm/flowgraph/mod.rs | 5 +- boa_engine/src/vm/mod.rs | 12 +- boa_engine/src/vm/opcode/call/mod.rs | 219 +++++++++++++++++- boa_engine/src/vm/opcode/mod.rs | 7 + boa_examples/src/bin/classes.rs | 2 +- boa_examples/src/bin/closures.rs | 13 +- boa_examples/src/bin/derive.rs | 2 +- boa_examples/src/bin/futures.rs | 2 +- boa_examples/src/bin/loadfile.rs | 2 +- boa_examples/src/bin/loadstring.rs | 2 +- boa_examples/src/bin/modulehandler.rs | 6 +- boa_examples/src/bin/runtime_limits.rs | 16 +- .../expression/left_hand_side/member.rs | 26 ++- boa_runtime/src/lib.rs | 2 +- boa_tester/src/exec/js262.rs | 12 +- boa_tester/src/exec/mod.rs | 20 +- boa_wasm/src/lib.rs | 2 +- 31 files changed, 640 insertions(+), 148 deletions(-) create mode 100644 boa_engine/src/script.rs diff --git a/boa_ast/src/expression/call.rs b/boa_ast/src/expression/call.rs index 5663e5acb07..8954d9e021f 100644 --- a/boa_ast/src/expression/call.rs +++ b/boa_ast/src/expression/call.rs @@ -162,3 +162,68 @@ impl VisitWith for SuperCall { ControlFlow::Continue(()) } } + +/// The import() syntax, commonly called dynamic import, is a function-like expression that allows +/// loading an ECMAScript module asynchronously and dynamically into a potentially non-module +/// environment. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ImportCall +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Clone, Debug, PartialEq)] +pub struct ImportCall { + arg: Box, +} + +impl ImportCall { + /// Creates a new `ImportCall` AST node. + pub fn new(arg: A) -> Self + where + A: Into, + { + Self { + arg: Box::new(arg.into()), + } + } + + /// Retrieves the single argument of the import call. + #[must_use] + pub const fn argument(&self) -> &Expression { + &self.arg + } +} + +impl ToInternedString for ImportCall { + #[inline] + fn to_interned_string(&self, interner: &Interner) -> String { + format!("import({})", self.arg.to_interned_string(interner)) + } +} + +impl From for Expression { + #[inline] + fn from(call: ImportCall) -> Self { + Self::ImportCall(call) + } +} + +impl VisitWith for ImportCall { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.arg) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.arg) + } +} diff --git a/boa_ast/src/expression/mod.rs b/boa_ast/src/expression/mod.rs index 3288ece120f..46462c7d910 100644 --- a/boa_ast/src/expression/mod.rs +++ b/boa_ast/src/expression/mod.rs @@ -33,7 +33,7 @@ mod tagged_template; mod r#yield; use crate::visitor::{VisitWith, Visitor, VisitorMut}; -pub use call::{Call, SuperCall}; +pub use call::{Call, ImportCall, SuperCall}; pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT}; pub use new::New; pub use optional::{Optional, OptionalOperation, OptionalOperationKind}; @@ -121,10 +121,12 @@ pub enum Expression { /// See [`SuperCall`]. SuperCall(SuperCall), + /// See [`ImportCall`]. + ImportCall(ImportCall), + /// See [`Optional`]. Optional(Optional), - // TODO: Import calls /// See [`TaggedTemplate`]. TaggedTemplate(TaggedTemplate), @@ -192,6 +194,7 @@ impl Expression { Self::New(new) => new.to_interned_string(interner), Self::Call(call) => call.to_interned_string(interner), Self::SuperCall(supc) => supc.to_interned_string(interner), + Self::ImportCall(impc) => impc.to_interned_string(interner), Self::Optional(opt) => opt.to_interned_string(interner), Self::NewTarget => "new.target".to_owned(), Self::TaggedTemplate(tag) => tag.to_interned_string(interner), @@ -300,6 +303,7 @@ impl VisitWith for Expression { Self::New(n) => visitor.visit_new(n), Self::Call(c) => visitor.visit_call(c), Self::SuperCall(sc) => visitor.visit_super_call(sc), + Self::ImportCall(ic) => visitor.visit_import_call(ic), Self::Optional(opt) => visitor.visit_optional(opt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt), Self::Assign(a) => visitor.visit_assign(a), @@ -341,6 +345,7 @@ impl VisitWith for Expression { Self::New(n) => visitor.visit_new_mut(n), Self::Call(c) => visitor.visit_call_mut(c), Self::SuperCall(sc) => visitor.visit_super_call_mut(sc), + Self::ImportCall(ic) => visitor.visit_import_call_mut(ic), Self::Optional(opt) => visitor.visit_optional_mut(opt), Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt), Self::Assign(a) => visitor.visit_assign_mut(a), diff --git a/boa_ast/src/visitor.rs b/boa_ast/src/visitor.rs index df017da17b4..dec4161b010 100644 --- a/boa_ast/src/visitor.rs +++ b/boa_ast/src/visitor.rs @@ -21,7 +21,7 @@ use crate::{ assign::{Assign, AssignTarget}, Binary, BinaryInPrivate, Conditional, Unary, Update, }, - Await, Call, Expression, Identifier, New, Optional, OptionalOperation, + Await, Call, Expression, Identifier, ImportCall, New, Optional, OptionalOperation, OptionalOperationKind, Parenthesized, Spread, SuperCall, TaggedTemplate, Yield, }, function::{ @@ -164,6 +164,7 @@ node_ref! { New, Call, SuperCall, + ImportCall, Optional, TaggedTemplate, Assign, @@ -263,6 +264,7 @@ pub trait Visitor<'ast>: Sized { define_visit!(visit_new, New); define_visit!(visit_call, Call); define_visit!(visit_super_call, SuperCall); + define_visit!(visit_import_call, ImportCall); define_visit!(visit_optional, Optional); define_visit!(visit_tagged_template, TaggedTemplate); define_visit!(visit_assign, Assign); @@ -359,6 +361,7 @@ pub trait Visitor<'ast>: Sized { NodeRef::New(n) => self.visit_new(n), NodeRef::Call(n) => self.visit_call(n), NodeRef::SuperCall(n) => self.visit_super_call(n), + NodeRef::ImportCall(n) => self.visit_import_call(n), NodeRef::Optional(n) => self.visit_optional(n), NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n), NodeRef::Assign(n) => self.visit_assign(n), @@ -460,6 +463,7 @@ pub trait VisitorMut<'ast>: Sized { define_visit_mut!(visit_new_mut, New); define_visit_mut!(visit_call_mut, Call); define_visit_mut!(visit_super_call_mut, SuperCall); + define_visit_mut!(visit_import_call_mut, ImportCall); define_visit_mut!(visit_optional_mut, Optional); define_visit_mut!(visit_tagged_template_mut, TaggedTemplate); define_visit_mut!(visit_assign_mut, Assign); @@ -556,6 +560,7 @@ pub trait VisitorMut<'ast>: Sized { NodeRefMut::New(n) => self.visit_new_mut(n), NodeRefMut::Call(n) => self.visit_call_mut(n), NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n), + NodeRefMut::ImportCall(n) => self.visit_import_call_mut(n), NodeRefMut::Optional(n) => self.visit_optional_mut(n), NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n), NodeRefMut::Assign(n) => self.visit_assign_mut(n), diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index e7e7210c7a3..0485e788ab6 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -70,6 +70,7 @@ use boa_engine::{ module::{Module, ModuleLoader, SimpleModuleLoader}, optimizer::OptimizerOptions, property::Attribute, + script::Script, vm::flowgraph::{Direction, Graph}, Context, JsNativeError, JsResult, Source, }; @@ -263,8 +264,8 @@ fn generate_flowgraph( format: FlowgraphFormat, direction: Option, ) -> JsResult { - let ast = context.parse_script(Source::from_bytes(src))?; - let code = context.compile_script(&ast)?; + let script = Script::parse(Source::from_bytes(src), None, context)?; + let code = script.codeblock(context)?; let direction = match direction { Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom, @@ -331,7 +332,7 @@ fn evaluate_files( Err(err) => eprintln!("Uncaught {err}"), } } else { - match context.eval_script(Source::from_bytes(&buffer)) { + match context.eval(Source::from_bytes(&buffer)) { Ok(v) => println!("{}", v.display()), Err(v) => eprintln!("Uncaught {v}"), } @@ -425,7 +426,7 @@ fn main() -> Result<(), io::Error> { Err(v) => eprintln!("Uncaught {v}"), } } else { - match context.eval_script(Source::from_bytes(line.trim_end())) { + match context.eval(Source::from_bytes(line.trim_end())) { Ok(v) => { println!("{}", v.display()); } diff --git a/boa_engine/benches/full.rs b/boa_engine/benches/full.rs index f59a92dca57..2478f187fb3 100644 --- a/boa_engine/benches/full.rs +++ b/boa_engine/benches/full.rs @@ -2,7 +2,7 @@ use boa_engine::{ context::DefaultHooks, object::shape::RootShape, optimizer::OptimizerOptions, realm::Realm, - Context, Source, + script::Script, Context, Source, }; use criterion::{criterion_group, criterion_main, Criterion}; use std::hint::black_box; @@ -33,7 +33,13 @@ macro_rules! full_benchmarks { context.set_optimizer_options(OptimizerOptions::empty()); c.bench_function(concat!($id, " (Parser)"), move |b| { - b.iter(|| context.parse_script(black_box(Source::from_bytes(CODE)))) + b.iter(|| { + Script::parse( + black_box(Source::from_bytes(CODE)), + None, + &mut context, + ).unwrap() + }) }); } )* @@ -42,15 +48,19 @@ macro_rules! full_benchmarks { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); - let mut context = Context::default(); + let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); - let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); + let script = Script::parse( + black_box(Source::from_bytes(CODE)), + None, + context, + ).unwrap(); c.bench_function(concat!($id, " (Compiler)"), move |b| { b.iter(|| { - context.compile_script(black_box(&statement_list)) + script.codeblock(context).unwrap() }) }); } @@ -60,16 +70,20 @@ macro_rules! full_benchmarks { $( { static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); - let mut context = Context::default(); + let context = &mut Context::default(); // Disable optimizations context.set_optimizer_options(OptimizerOptions::empty()); - let statement_list = context.parse_script(Source::from_bytes(CODE)).expect("parsing failed"); - let code_block = context.compile_script(&statement_list).unwrap(); + let script = Script::parse( + black_box(Source::from_bytes(CODE)), + None, + context, + ).unwrap(); + script.codeblock(context).unwrap(); c.bench_function(concat!($id, " (Execution)"), move |b| { b.iter(|| { - context.execute(black_box(code_block.clone())).unwrap() + script.evaluate(context).unwrap(); }) }); } diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 05d01ae258a..0f35d572c39 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -10,8 +10,14 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval use crate::{ - builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics, - environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode, + builtins::BuiltInObject, + bytecompiler::ByteCompiler, + context::intrinsics::Intrinsics, + environments::Environment, + error::JsNativeError, + object::JsObject, + realm::Realm, + vm::{CallFrame, Opcode}, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; @@ -246,6 +252,11 @@ impl Eval { context.vm.environments.extend_outer_function_environment(); } - context.execute(code_block) + context.vm.push_frame(CallFrame::new(code_block)); + context.realm().resize_global_env(); + let record = context.run(); + context.vm.pop_frame(); + + record.consume() } } diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 26fedf0cf31..fa47e645c7a 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -29,6 +29,7 @@ use crate::{ string::{utf16, CodePoint}, symbol::JsSymbol, value::IntegerOrInfinity, + vm::CallFrame, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_gc::Gc; @@ -126,7 +127,13 @@ impl Json { compiler.compile_statement_list(&statement_list, true, false); Gc::new(compiler.finish()) }; - let unfiltered = context.execute(code_block)?; + + context.vm.push_frame(CallFrame::new(code_block)); + context.realm().resize_global_env(); + let record = context.run(); + context.vm.pop_frame(); + + let unfiltered = record.consume()?; // 11. If IsCallable(reviver) is true, then if let Some(obj) = args.get_or_undefined(1).as_callable() { diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index 2203c95d4c9..2351dd6e47f 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -320,6 +320,13 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Pop); } } + Expression::ImportCall(import) => { + self.compile_expr(import.argument(), true); + self.emit_opcode(Opcode::ImportCall); + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } Expression::NewTarget => { if use_expr { self.emit_opcode(Opcode::PushNewTarget); diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5d8551a8202..9c1b9c2e7f2 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -18,7 +18,6 @@ use std::{io::Read, path::Path, rc::Rc}; use crate::{ builtins, - bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, job::{JobQueue, NativeJob, SimpleJobQueue}, module::{ModuleLoader, SimpleModuleLoader}, @@ -27,13 +26,12 @@ use crate::{ optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, - vm::{CallFrame, CodeBlock, Vm}, + script::Script, + vm::{CallFrame, Vm}, JsResult, JsValue, Source, }; use boa_ast::{expression::Identifier, StatementList}; -use boa_gc::Gc; -use boa_interner::{Interner, Sym}; -use boa_parser::Parser; +use boa_interner::Interner; use boa_profiler::Profiler; use crate::vm::RuntimeLimits; @@ -153,7 +151,7 @@ impl<'host> Context<'host> { ContextBuilder::default() } - /// Evaluates the given script `src` by compiling down to bytecode, then interpreting the + /// Evaluates the given source by compiling down to bytecode, then interpreting the /// bytecode into a value. /// /// # Examples @@ -162,7 +160,7 @@ impl<'host> Context<'host> { /// let mut context = Context::default(); /// /// let source = Source::from_bytes("1 + 3"); - /// let value = context.eval_script(source).unwrap(); + /// let value = context.eval(source).unwrap(); /// /// assert!(value.is_number()); /// assert_eq!(value.as_number().unwrap(), 4.0); @@ -171,12 +169,10 @@ impl<'host> Context<'host> { /// 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. #[allow(clippy::unit_arg, clippy::drop_copy)] - pub fn eval_script(&mut self, src: Source<'_, R>) -> JsResult { + pub fn eval(&mut self, src: Source<'_, R>) -> JsResult { let main_timer = Profiler::global().start_event("Script evaluation", "Main"); - let script = self.parse_script(src)?; - let code_block = self.compile_script(&script)?; - let result = self.execute(code_block); + let result = Script::parse(src, None, self)?.evaluate(self); // The main_timer needs to be dropped before the Profiler is. drop(main_timer); @@ -194,61 +190,6 @@ impl<'host> Context<'host> { optimizer.apply(statement_list) } - /// Parse the given source script. - pub fn parse_script(&mut self, src: Source<'_, R>) -> JsResult { - let _timer = Profiler::global().start_event("Script parsing", "Main"); - let mut parser = Parser::new(src); - parser.set_identifier(self.next_parser_identifier()); - if self.strict { - parser.set_strict(); - } - let mut result = parser.parse_script(&mut self.interner)?; - if !self.optimizer_options().is_empty() { - self.optimize_statement_list(&mut result); - } - Ok(result) - } - - /// Compile the script AST into a `CodeBlock` ready to be executed by the VM. - pub fn compile_script(&mut self, statement_list: &StatementList) -> JsResult> { - let _timer = Profiler::global().start_event("Script compilation", "Main"); - - let mut compiler = ByteCompiler::new( - Sym::MAIN, - statement_list.strict(), - false, - self.realm.environment().compile_env(), - self, - ); - compiler.global_declaration_instantiation(statement_list)?; - compiler.compile_statement_list(statement_list, true, false); - Ok(Gc::new(compiler.finish())) - } - - /// Call the VM with a `CodeBlock` and return the result. - /// - /// Since this function receives a `Gc`, cloning the code is very cheap, since it's - /// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple - /// times, there is no need to re-compile it, and you can just call `clone()` on the - /// `Gc` returned by the [`Context::compile_script`] function. - /// - /// 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. - pub fn execute(&mut self, code_block: Gc) -> JsResult { - let _timer = Profiler::global().start_event("Execution", "Main"); - - self.vm.push_frame(CallFrame::new(code_block)); - - // TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation - - self.realm().resize_global_env(); - let record = self.run(); - self.vm.pop_frame(); - self.clear_kept_objects(); - - record.consume() - } - /// Register a global property. /// /// It will return an error if the property is already defined. @@ -737,6 +678,11 @@ impl Context<'_> { // 6. Return true. Ok(true) } + + /// Returns `true` if this context is in strict mode. + pub(crate) const fn is_strict(&self) -> bool { + self.strict + } } impl<'host> Context<'host> { diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index d7d8a185505..bd96651fd0d 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -137,6 +137,7 @@ pub mod object; pub mod optimizer; pub mod property; pub mod realm; +pub mod script; pub mod string; pub mod symbol; // pub(crate) mod tagged; @@ -153,6 +154,7 @@ pub mod prelude { module::Module, native_function::NativeFunction, object::JsObject, + script::Script, Context, JsBigInt, JsResult, JsString, JsValue, }; pub use boa_parser::Source; @@ -336,7 +338,7 @@ fn run_test_actions(actions: impl IntoIterator) { fn run_test_actions_with(actions: impl IntoIterator, context: &mut Context<'_>) { #[track_caller] fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult { - context.eval_script(Source::from_bytes(source)) + context.eval(Source::from_bytes(source)) } #[track_caller] diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs index a1b25e246e3..3105821092d 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -41,6 +41,8 @@ use boa_parser::{Parser, Source}; use boa_profiler::Profiler; use crate::object::FunctionObjectBuilder; +use crate::script::Script; +use crate::vm::ActiveRunnable; use crate::{ builtins::promise::{PromiseCapability, PromiseState}, environments::DeclarativeEnvironment, @@ -51,12 +53,23 @@ use crate::{ use crate::{js_string, JsNativeError, NativeFunction}; /// The referrer from which a load request of a module originates. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Referrer { /// A [**Source Text Module Record**](https://tc39.es/ecma262/#sec-source-text-module-records). Module(Module), /// A [**Realm**](https://tc39.es/ecma262/#sec-code-realms). - Realm(Realm), // TODO: script + Realm(Realm), + /// A [**Script Record**] + Script(Script), +} + +impl From for Referrer { + fn from(value: ActiveRunnable) -> Self { + match value { + ActiveRunnable::Script(script) => Referrer::Script(script), + ActiveRunnable::Module(module) => Referrer::Module(module), + } + } } /// Module loading related host hooks. diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index bc4afc52a79..661d69cb314 100644 --- a/boa_engine/src/module/source.rs +++ b/boa_engine/src/module/source.rs @@ -22,7 +22,7 @@ use crate::{ module::ModuleKind, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, realm::Realm, - vm::{CallFrame, CodeBlock, CompletionRecord, Opcode}, + vm::{ActiveRunnable, CallFrame, CodeBlock, CompletionRecord, Opcode}, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, }; @@ -1564,10 +1564,15 @@ impl SourceTextModule { }; // 8. Let moduleContext be a new ECMAScript code execution context. - // 12. Set the ScriptOrModule of moduleContext to module. let mut envs = EnvironmentStack::new(global_env); envs.push_module(module_compile_env); + // 12. Set the ScriptOrModule of moduleContext to module. + let active_runnable = context + .vm + .active_runnable + .replace(ActiveRunnable::Module(parent.clone())); + // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. // 15. Set the PrivateEnvironment of moduleContext to null. @@ -1623,6 +1628,7 @@ impl SourceTextModule { std::mem::swap(&mut context.vm.environments, &mut envs); context.vm.stack = stack; context.vm.active_function = active_function; + context.vm.active_runnable = active_runnable; context.swap_realm(&mut realm); debug_assert!(envs.current().as_declarative().is_some()); @@ -1674,6 +1680,11 @@ impl SourceTextModule { callframe.promise_capability = capability; // 4. Set the ScriptOrModule of moduleContext to module. + let active_runnable = context + .vm + .active_runnable + .replace(ActiveRunnable::Module(self.parent())); + // 5. Assert: module has been linked and declarations in its module environment have been instantiated. // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. @@ -1700,6 +1711,7 @@ impl SourceTextModule { std::mem::swap(&mut context.vm.environments, &mut environments); context.vm.stack = stack; context.vm.active_function = function; + context.vm.active_runnable = active_runnable; context.swap_realm(&mut realm); context.vm.pop_frame(); @@ -1712,6 +1724,11 @@ impl SourceTextModule { Ok(()) } } + + /// Gets the loaded modules of this module. + pub(crate) fn loaded_modules(&self) -> &GcRefCell> { + &self.inner.loaded_modules + } } /// Abstract operation [`AsyncModuleExecutionFulfilled ( module )`][spec]. diff --git a/boa_engine/src/script.rs b/boa_engine/src/script.rs new file mode 100644 index 00000000000..16f59740104 --- /dev/null +++ b/boa_engine/src/script.rs @@ -0,0 +1,162 @@ +//! 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_ast::StatementList; +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, +} + +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.code) + .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] + code: StatementList, + codeblock: GcRefCell>>, + loaded_modules: GcRefCell>, + 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> { + &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( + src: Source<'_, R>, + realm: Option, + context: &mut Context<'_>, + ) -> JsResult