use super::from_js_error;
#[cfg(feature = "json")]
use crate::apis::json;
use crate::{
Config,
apis::{console, random, stream_io, text_encoding},
config::{JSIntrinsics, JavyIntrinsics},
};
use anyhow::{Result, bail};
use rquickjs::{
Context, Module, Runtime as QRuntime, WriteOptions,
context::{Intrinsic, intrinsic},
};
use std::mem::ManuallyDrop;
pub struct Runtime {
context: ManuallyDrop<Context>,
inner: ManuallyDrop<QRuntime>,
}
impl Runtime {
pub fn new(config: Config) -> Result<Self> {
let rt = ManuallyDrop::new(QRuntime::new()?);
let context = Self::build_from_config(&rt, config)?;
Ok(Self { inner: rt, context })
}
fn build_from_config(rt: &QRuntime, cfg: Config) -> Result<ManuallyDrop<Context>> {
let cfg = cfg.validate()?;
let intrinsics = &cfg.intrinsics;
let javy_intrinsics = &cfg.javy_intrinsics;
rt.set_gc_threshold(cfg.gc_threshold);
rt.set_memory_limit(cfg.memory_limit);
rt.set_max_stack_size(cfg.max_stack_size);
let context = Context::base(rt)?;
context.with(|ctx| {
random::register(ctx.clone()).expect("registering `random` APIs to succeed");
if intrinsics.contains(JSIntrinsics::DATE) {
unsafe { intrinsic::Date::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::EVAL) {
unsafe { intrinsic::Eval::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::REGEXP_COMPILER) {
unsafe { intrinsic::RegExpCompiler::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::REGEXP) {
unsafe { intrinsic::RegExp::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::JSON) {
unsafe { intrinsic::Json::add_intrinsic(ctx.as_raw()) }
}
#[cfg(feature = "json")]
if cfg.simd_json_builtins {
json::register(ctx.clone()).expect("registering JSON builtins to succeed");
}
if intrinsics.contains(JSIntrinsics::PROXY) {
unsafe { intrinsic::Proxy::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::MAP_SET) {
unsafe { intrinsic::MapSet::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::TYPED_ARRAY) {
unsafe { intrinsic::TypedArrays::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::PROMISE) {
unsafe { intrinsic::Promise::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::BIG_INT) {
unsafe { intrinsic::BigInt::add_intrinsic(ctx.as_raw()) }
}
if intrinsics.contains(JSIntrinsics::TEXT_ENCODING) {
text_encoding::register(ctx.clone())
.expect("registering TextEncoding APIs to succeed");
}
if intrinsics.contains(JSIntrinsics::WEAK_REF) {
unsafe { intrinsic::WeakRef::add_intrinsic(ctx.as_raw()) };
}
if intrinsics.contains(JSIntrinsics::PERFORMANCE) {
unsafe { intrinsic::Performance::add_intrinsic(ctx.as_raw()) };
}
console::register(ctx.clone(), cfg.log_stream, cfg.err_stream)
.expect("registering console to succeed");
if javy_intrinsics.contains(JavyIntrinsics::STREAM_IO) {
stream_io::register(ctx.clone())
.expect("registering StreamIO functions to succeed");
}
});
Ok(ManuallyDrop::new(context))
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn resolve_pending_jobs(&self) -> Result<()> {
if self.inner.is_job_pending() {
loop {
let result = self.inner.execute_pending_job();
if let Ok(false) = result {
break;
}
if let Err(e) = result {
bail!("{e}")
}
}
}
Ok(())
}
pub fn has_pending_jobs(&self) -> bool {
self.inner.is_job_pending()
}
pub fn compile_to_bytecode(&self, name: &str, contents: &str) -> Result<Vec<u8>> {
self.context()
.with(|this| {
Module::declare(this.clone(), name, contents)?.write(WriteOptions::default())
})
.map_err(|e| self.context().with(|cx| from_js_error(cx.clone(), e)))
}
}
impl Default for Runtime {
fn default() -> Self {
Self::new(Config::default()).unwrap()
}
}