1use std::rc::Rc;
6
7use oxide_core::vm::compile::{CompileError, Compiler};
8
9use super::RuntimeError;
10use super::compile::{
11 ModuleCompileError, compile_module, front_compile_error, normalize_module_name,
12};
13use super::function::{FunctionOptions, HostFunction, host_body};
14use super::runtime::{Preloaded, Shared, current, slot};
15use super::value::{CallableRef, ObjectRef, SymbolRef, Value};
16use crate::engine::api::error::NativeErrorKind;
17use crate::engine::code::module::{ModuleImportAttribute, ModuleImportAttributes};
18use crate::engine::value::JsString;
19
20pub enum ModuleSource {
22 Source {
25 text: Vec<u8>,
26 import_meta: Vec<(String, Value)>,
27 },
28 Json(Vec<u8>),
30 Json5(Vec<u8>),
32 Compiled(CompiledModule),
34 Synthetic { exports: Vec<(String, Value)> },
37}
38
39impl ModuleSource {
40 pub fn source(text: impl Into<Vec<u8>>) -> Self {
42 ModuleSource::Source {
43 text: text.into(),
44 import_meta: Vec::new(),
45 }
46 }
47}
48
49pub struct CompiledModule {
51 function: ObjectRef,
52 tables: oxide_core::vm::module::ModuleTables,
53 import_meta: Vec<(String, Value)>,
54}
55
56pub trait ModuleLoader {
59 fn normalize(
63 &mut self,
64 scope: &mut ModuleScope<'_>,
65 referrer: &str,
66 specifier: &str,
67 attributes: &ModuleImportAttributes,
68 ) -> Result<String, Value> {
69 let _ = (scope, attributes);
70 Ok(normalize_module_name(referrer, specifier))
71 }
72
73 fn load(
75 &mut self,
76 scope: &mut ModuleScope<'_>,
77 name: &str,
78 attributes: &ModuleImportAttributes,
79 ) -> Result<ModuleSource, Value>;
80}
81
82pub struct ModuleScope<'a> {
88 compiler: Compiler<'a>,
89 shared: &'a Rc<Shared>,
90}
91
92impl ModuleScope<'_> {
93 fn hold(&mut self, value: oxide_core::Value) -> Value {
94 use oxide_core::value::Kind;
95 match value.kind() {
96 Kind::Object => Value::Object(ObjectRef {
97 slot: slot(self.shared, self.compiler.hold(value), value),
98 }),
99 Kind::Symbol => Value::Symbol(SymbolRef {
100 slot: slot(self.shared, self.compiler.hold(value), value),
101 }),
102 Kind::String => {
103 let units: Vec<u16> = self
104 .compiler
105 .heap()
106 .string(value.as_string().expect("a string"))
107 .repr()
108 .units()
109 .collect();
110 JsString::try_from_utf16(units).map_or(Value::Undefined, Value::String)
111 }
112 Kind::Bool => Value::Bool(value.as_bool().unwrap_or(false)),
113 Kind::Int32 => Value::Int(value.as_int32().unwrap_or(0)),
114 Kind::Number => Value::Float(value.as_f64().unwrap_or(f64::NAN)),
115 Kind::Null => Value::Null,
116 _ => Value::Undefined,
117 }
118 }
119
120 pub fn new_object(&mut self) -> Result<ObjectRef, RuntimeError> {
122 let prototype = self
123 .compiler
124 .intrinsic(oxide_core::Intrinsic::ObjectPrototype);
125 let object = self.compiler.heap_mut().alloc_object(
126 oxide_core::ObjectKind::Ordinary,
127 Some(prototype),
128 4,
129 )?;
130 match self.hold(oxide_core::Value::object(object)) {
131 Value::Object(object) => Ok(object),
132 _ => Err(RuntimeError::Invariant("a new object")),
133 }
134 }
135
136 pub fn new_function<Args>(
139 &mut self,
140 options: FunctionOptions,
141 function: impl HostFunction<Args>,
142 ) -> Result<CallableRef, RuntimeError> {
143 let mut data = Vec::with_capacity(options.data.len());
144 for value in &options.data {
145 data.push(self.lower(value));
146 }
147 let body = host_body(self.shared, function);
148 let object = self.compiler.new_host_function(
149 &options.name,
150 options.length,
151 options.constructor,
152 &data,
153 body,
154 )?;
155 match self.hold(oxide_core::Value::object(object)) {
156 Value::Object(object) => Ok(CallableRef(object)),
157 _ => Err(RuntimeError::Invariant("a new function")),
158 }
159 }
160
161 pub fn new_host_object<T: 'static>(
164 &mut self,
165 value: T,
166 prototype: Option<&ObjectRef>,
167 ) -> Result<ObjectRef, RuntimeError> {
168 let prototype = prototype.and_then(|prototype| prototype.core_value().as_object());
169 let object = self
170 .compiler
171 .new_host_object(prototype, std::rc::Rc::new(value))?;
172 match self.hold(oxide_core::Value::object(object)) {
173 Value::Object(object) => Ok(object),
174 _ => Err(RuntimeError::Invariant("a new host object")),
175 }
176 }
177
178 pub fn new_native_error(
180 &mut self,
181 kind: NativeErrorKind,
182 message: &str,
183 ) -> Result<Value, RuntimeError> {
184 let error = self.compiler.new_error(core_error_kind(kind), message)?;
185 Ok(self.hold(error))
186 }
187
188 pub fn compile_module(
191 &mut self,
192 name: &str,
193 text: &[u8],
194 import_meta: Vec<(String, Value)>,
195 ) -> Result<CompiledModule, Value> {
196 let debug_info = self.shared.debug_info.get();
197 match compile_module(&mut self.compiler, name, text, debug_info) {
198 Ok((
199 oxide_core::vm::module::ModuleSource::Compiled {
200 function, tables, ..
201 },
202 _,
203 )) => {
204 let Value::Object(function) = self.hold(oxide_core::Value::object(function)) else {
205 unreachable!("a module function is an object");
206 };
207 Ok(CompiledModule {
208 function,
209 tables,
210 import_meta,
211 })
212 }
213 Ok(_) => Err(Value::Undefined),
214 Err(error) => {
215 let failure = match error {
216 ModuleCompileError::Front(error) => front_compile_error(&error, text, name),
217 ModuleCompileError::Core(error) => error,
218 };
219 let thrown = throw_value(&mut self.compiler, &failure);
220 Err(self.hold(thrown))
221 }
222 }
223 }
224
225 fn lower(&mut self, value: &Value) -> oxide_core::Value {
227 let lowered = match value {
228 Value::Undefined => oxide_core::Value::UNDEFINED,
229 Value::Null => oxide_core::Value::NULL,
230 Value::Bool(value) => oxide_core::Value::bool(*value),
231 Value::Int(value) => oxide_core::Value::int32(*value),
232 Value::Float(value) => oxide_core::Value::number(*value),
233 Value::String(text) => {
234 let units: Vec<u16> = text.utf16_units().collect();
235 match self.compiler.heap_mut().alloc_string_units(&units) {
236 Ok(id) => oxide_core::Value::string(id),
237 Err(_) => oxide_core::Value::UNDEFINED,
238 }
239 }
240 Value::BigInt(_) => oxide_core::Value::UNDEFINED,
241 Value::Symbol(symbol) => symbol.core_value(),
242 Value::Object(object) => object.core_value(),
243 };
244 if lowered.is_heap() {
246 let handle = self.compiler.hold(lowered);
247 self.shared.release(handle);
248 }
249 lowered
250 }
251}
252
253fn core_error_kind(kind: NativeErrorKind) -> oxide_core::ErrorKind {
254 match kind {
255 NativeErrorKind::Eval => oxide_core::ErrorKind::Eval,
256 NativeErrorKind::Range => oxide_core::ErrorKind::Range,
257 NativeErrorKind::Reference => oxide_core::ErrorKind::Reference,
258 NativeErrorKind::Syntax => oxide_core::ErrorKind::Syntax,
259 NativeErrorKind::Type => oxide_core::ErrorKind::Type,
260 NativeErrorKind::Uri => oxide_core::ErrorKind::Uri,
261 NativeErrorKind::Internal => oxide_core::ErrorKind::Internal,
262 NativeErrorKind::Aggregate => oxide_core::ErrorKind::Aggregate,
263 }
264}
265
266fn throw_value(compiler: &mut Compiler<'_>, failure: &CompileError) -> oxide_core::Value {
269 let created = match &failure.location {
270 Some((file, line, column)) => {
271 compiler.new_located_error(failure.kind, &failure.message, file, *line, *column)
272 }
273 None => compiler.new_error(failure.kind, &failure.message),
274 };
275 created.unwrap_or(oxide_core::Value::UNDEFINED)
276}
277
278fn attributes(pairs: &[(String, String)]) -> ModuleImportAttributes {
280 if pairs.is_empty() {
281 return ModuleImportAttributes::Absent;
282 }
283 let attributes: Vec<ModuleImportAttribute> = pairs
284 .iter()
285 .filter_map(|(key, value)| {
286 Some(ModuleImportAttribute {
287 key: JsString::try_from_utf8(key).ok()?,
288 value: JsString::try_from_utf8(value).ok()?,
289 })
290 })
291 .collect();
292 ModuleImportAttributes::Present(attributes.into_boxed_slice())
293}
294
295pub(crate) fn normalize_shim(
297 compiler: &mut Compiler<'_>,
298 referrer: &str,
299 specifier: &str,
300 pairs: &[(String, String)],
301) -> Result<String, CompileError> {
302 let Some(shared) = current() else {
303 return Ok(normalize_module_name(referrer, specifier));
304 };
305 let Ok(mut loader) = shared.loader.try_borrow_mut() else {
306 return Err(CompileError::new(
307 oxide_core::ErrorKind::Internal,
308 "the module loader was re-entered",
309 ));
310 };
311 let Some(loader) = loader.as_mut() else {
312 return Ok(normalize_module_name(referrer, specifier));
313 };
314 let mut scope = ModuleScope {
315 compiler: compiler.reborrow(),
316 shared: &shared,
317 };
318 loader
319 .normalize(&mut scope, referrer, specifier, &attributes(pairs))
320 .map_err(|thrown| CompileError::thrown(scope.lower(&thrown)))
321}
322
323pub(crate) fn load_shim(
326 compiler: &mut Compiler<'_>,
327 name: &str,
328 pairs: &[(String, String)],
329) -> Result<oxide_core::vm::module::ModuleSource, CompileError> {
330 let Some(shared) = current() else {
331 return Err(CompileError::new(
332 oxide_core::ErrorKind::Internal,
333 "unsupported: modules",
334 ));
335 };
336 let preloaded = shared.modules.borrow_mut().remove(name);
337 if let Some(preloaded) = preloaded {
338 let mut scope = ModuleScope {
339 compiler: compiler.reborrow(),
340 shared: &shared,
341 };
342 return Ok(match preloaded {
343 Preloaded::Compiled {
344 function,
345 tables,
346 import_meta,
347 } => compiled_source(&mut scope, function, tables, &import_meta),
348 Preloaded::Synthetic(exports) => synthetic_source(&mut scope, &exports),
349 });
350 }
351 let Ok(mut loader) = shared.loader.try_borrow_mut() else {
352 return Err(CompileError::new(
353 oxide_core::ErrorKind::Internal,
354 "the module loader was re-entered",
355 ));
356 };
357 let Some(loader) = loader.as_mut() else {
358 return Err(CompileError::new(
359 oxide_core::ErrorKind::Reference,
360 format!("could not load module filename '{name}'"),
361 ));
362 };
363 let mut scope = ModuleScope {
364 compiler: compiler.reborrow(),
365 shared: &shared,
366 };
367 let source = loader
368 .load(&mut scope, name, &attributes(pairs))
369 .map_err(|thrown| CompileError::thrown(scope.lower(&thrown)))?;
370 match source {
371 ModuleSource::Source { text, import_meta } => {
372 let module = scope
373 .compile_module(name, &text, import_meta)
374 .map_err(|thrown| CompileError::thrown(scope.lower(&thrown)))?;
375 Ok(compiled_source(
376 &mut scope,
377 module.function,
378 module.tables,
379 &module.import_meta,
380 ))
381 }
382 ModuleSource::Compiled(module) => Ok(compiled_source(
383 &mut scope,
384 module.function,
385 module.tables,
386 &module.import_meta,
387 )),
388 ModuleSource::Synthetic { exports } => Ok(synthetic_source(&mut scope, &exports)),
389 ModuleSource::Json(text) => {
390 Ok(oxide_core::vm::module::ModuleSource::Json(json_text(&text)))
391 }
392 ModuleSource::Json5(text) => Ok(oxide_core::vm::module::ModuleSource::Json5(json_text(
393 &text,
394 ))),
395 }
396}
397
398fn synthetic_source(
401 scope: &mut ModuleScope<'_>,
402 exports: &[(String, Value)],
403) -> oxide_core::vm::module::ModuleSource {
404 oxide_core::vm::module::ModuleSource::Synthetic {
405 exports: exports
406 .iter()
407 .map(|(name, value)| (name.clone(), scope.lower(value)))
408 .collect(),
409 }
410}
411
412fn json_text(bytes: &[u8]) -> oxide_core::vm::module::JsonText {
414 oxide_core::vm::module::JsonText::from_bytes(bytes, crate::engine::value::decode_quickjs_utf8)
415}
416
417fn compiled_source(
418 scope: &mut ModuleScope<'_>,
419 function: ObjectRef,
420 tables: oxide_core::vm::module::ModuleTables,
421 import_meta: &[(String, Value)],
422) -> oxide_core::vm::module::ModuleSource {
423 let import_meta = import_meta
424 .iter()
425 .map(|(key, value)| (key.clone(), scope.lower(value)))
426 .collect();
427 let id = function
428 .core_value()
429 .as_object()
430 .expect("a module function");
431 let _ = scope.lower(&Value::Object(function));
433 oxide_core::vm::module::ModuleSource::Compiled {
434 function: id,
435 tables,
436 import_meta,
437 }
438}