1use std::path::{Path, PathBuf};
12
13use rustc_hash::FxHashMap;
14
15use boa_gc::{Finalize, Gc, GcRefCell, Trace};
16use boa_parser::{Parser, Source, source::ReadChar};
17
18use crate::{
19 Context, HostDefined, JsResult, JsString, JsValue, Module, SpannedSourceText,
20 bytecompiler::{ByteCompiler, global_declaration_instantiation_context},
21 environments::EnvironmentStack,
22 js_string,
23 realm::Realm,
24 spanned_source_text::SourceText,
25 vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
26};
27
28#[derive(Clone, Trace, Finalize)]
32pub struct Script {
33 inner: Gc<Inner>,
34}
35
36impl std::fmt::Debug for Script {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("Script")
39 .field("realm", &self.inner.realm.addr())
40 .field("phase", &self.inner.phase.borrow())
41 .field("loaded_modules", &self.inner.loaded_modules)
42 .finish()
43 }
44}
45
46#[derive(Trace, Debug, Finalize)]
47enum ScriptPhase {
48 Ast(#[unsafe_ignore_trace] boa_ast::Script),
49 Codeblock(Gc<CodeBlock>),
50}
51
52#[derive(Trace, Finalize)]
53struct Inner {
54 realm: Realm,
55 phase: GcRefCell<ScriptPhase>,
56 source_text: SourceText,
57 loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
58 host_defined: HostDefined,
59 path: Option<PathBuf>,
60}
61
62impl Script {
63 #[must_use]
65 pub fn realm(&self) -> &Realm {
66 &self.inner.realm
67 }
68
69 #[must_use]
73 pub fn host_defined(&self) -> &HostDefined {
74 &self.inner.host_defined
75 }
76
77 pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<JsString, Module>> {
79 &self.inner.loaded_modules
80 }
81
82 pub fn parse<R: ReadChar>(
88 src: Source<'_, R>,
89 realm: Option<Realm>,
90 context: &mut Context,
91 ) -> JsResult<Self> {
92 let path = src.path().map(Path::to_path_buf);
93 let mut parser = Parser::new(src);
94 parser.set_identifier(context.next_parser_identifier());
95 if context.is_strict() {
96 parser.set_strict();
97 }
98 let scope = context.realm().scope().clone();
99 let (mut code, source) = parser.parse_script_with_source(&scope, context.interner_mut())?;
100 if !context.optimizer_options().is_empty() {
101 context.optimize_statement_list(code.statements_mut());
102 }
103
104 let source_text = SourceText::new(source);
105
106 Ok(Self {
107 inner: Gc::new(Inner {
108 realm: realm.unwrap_or_else(|| context.realm().clone()),
109 phase: GcRefCell::new(ScriptPhase::Ast(code)),
110 source_text,
111 loaded_modules: GcRefCell::default(),
112 host_defined: HostDefined::default(),
113 path,
114 }),
115 })
116 }
117
118 pub fn codeblock(&self, context: &mut Context) -> JsResult<Gc<CodeBlock>> {
122 let cb = {
123 let phase = self.inner.phase.borrow();
124 let source = match &*phase {
125 ScriptPhase::Codeblock(codeblock) => return Ok(codeblock.clone()),
126 ScriptPhase::Ast(source) => source,
127 };
128
129 let mut annex_b_function_names = Vec::new();
130
131 global_declaration_instantiation_context(
132 &mut annex_b_function_names,
133 source,
134 self.inner.realm.scope(),
135 context,
136 )?;
137
138 let spanned_source_text = SpannedSourceText::new_source_only(self.get_source());
139
140 let mut compiler = ByteCompiler::new(
141 js_string!("<main>"),
142 source.strict(),
143 false,
144 self.inner.realm.scope().clone(),
145 self.inner.realm.scope().clone(),
146 false,
147 false,
148 context.interner_mut(),
149 false,
150 spanned_source_text,
151 self.path().map(Path::to_owned).into(),
152 );
153
154 #[cfg(feature = "annex-b")]
155 {
156 compiler.annex_b_function_names = annex_b_function_names;
157 }
158
159 compiler.global_declaration_instantiation(source);
160 compiler.compile_statement_list(source.statements(), true, false);
161
162 Gc::new(compiler.finish())
163 };
164
165 *self.inner.phase.borrow_mut() = ScriptPhase::Codeblock(cb.clone());
166
167 Ok(cb)
168 }
169
170 pub fn evaluate(&self, context: &mut Context) -> JsResult<JsValue> {
177 self.prepare_run(context)?;
178 let record = context.run();
179
180 context.vm.pop_frame();
181
182 record.consume()
183 }
184
185 #[allow(clippy::future_not_send)]
192 pub async fn evaluate_async(&self, context: &mut Context) -> JsResult<JsValue> {
193 self.evaluate_async_with_budget(context, 256).await
194 }
195
196 #[allow(clippy::future_not_send)]
204 pub async fn evaluate_async_with_budget(
205 &self,
206 context: &mut Context,
207 budget: u32,
208 ) -> JsResult<JsValue> {
209 self.prepare_run(context)?;
210
211 let record = context.run_async_with_budget(budget).await;
212
213 context.vm.pop_frame();
214
215 record.consume()
216 }
217
218 fn prepare_run(&self, context: &mut Context) -> JsResult<()> {
219 let codeblock = self.codeblock(context)?;
220
221 let global_env = EnvironmentStack::new();
222 context.vm.push_frame_with_stack(
223 CallFrame::new(
224 codeblock.clone(),
225 Some(ActiveRunnable::Script(self.clone())),
226 global_env,
227 self.inner.realm.clone(),
228 )
229 .with_env_fp(0)
230 .with_flags(CallFrameFlags::EXIT_EARLY),
231 JsValue::undefined(),
232 JsValue::null(),
233 );
234
235 self.realm().resize_global_env();
236
237 context
238 .global_declaration_instantiation(&codeblock)
239 .inspect_err(|_| {
240 context.vm.pop_frame();
241 })?;
242
243 Ok(())
244 }
245
246 pub(super) fn path(&self) -> Option<&Path> {
247 self.inner.path.as_deref()
248 }
249
250 pub(super) fn get_source(&self) -> SourceText {
251 self.inner.source_text.clone()
252 }
253}