use std::rc::Rc;
use boa_engine::builtins::promise::PromiseState;
use boa_engine::context::ContextBuilder;
use boa_engine::job::JobQueue;
use boa_engine::module::ModuleLoader;
use boa_engine::property::Attribute;
use boa_engine::{
js_str, js_string, Context, JsError, JsResult, JsString, JsValue, Module, Source,
};
use boa_runtime::Console;
use crate::builtins;
use crate::runtime;
use crate::typescript;
pub struct ScriptExtension {
pub path: String,
pub transpile: bool,
}
pub struct DefaultScriptExtension {
pub script: &'static str,
pub transpile: bool,
}
pub struct Tanxium {
pub context: Context,
pub options: TanxiumOptions,
}
pub struct TanxiumBuiltinsExposure {
pub crypto: bool,
pub performance: bool,
pub runtime: bool,
pub console: bool,
}
pub struct TanxiumOptions {
pub cwd: String,
pub typescript: bool,
pub builtins: TanxiumBuiltinsExposure,
pub global_object_name: String,
}
impl Tanxium {
pub fn new(options: TanxiumOptions) -> Result<Self, std::io::Error> {
let job_queue = Rc::new(runtime::event_loop::EventLoop::new());
let module_loader = Rc::new(runtime::module_loader::YasumuModuleLoader::new(
options.cwd.clone(),
options.typescript.clone(),
));
let context = ContextBuilder::new()
.job_queue(job_queue)
.module_loader(module_loader)
.build()
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to create context: {}", e),
)
})?;
Ok(Tanxium { context, options })
}
pub fn initialize_runtime(&mut self) -> Result<(), std::io::Error> {
self.init_runtime_apis().map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to initialize runtime: {}", e),
)
})?;
self.load_default_extensions()?;
Ok(())
}
pub fn init_runtime_apis(&mut self) -> Result<(), JsError> {
let ctx = &mut self.context;
ctx.register_global_property(
js_str!("__IDENTIFIER__"),
js_string!(self.options.global_object_name.clone()),
Attribute::all(),
)?;
builtins::crypto::crypto_init(self)?;
builtins::performance::performance_init(self)?;
builtins::runtime_object::runtime_object_init(self)?;
if self.options.builtins.console {
let console = Console::init(&mut self.context);
self.context.register_global_property(
Console::NAME,
console,
boa_engine::property::Attribute::all(),
)?;
}
Ok(())
}
pub fn get_event_loop(&self) -> Rc<dyn JobQueue> {
self.context.job_queue()
}
pub fn get_module_loader(&self) -> Rc<dyn ModuleLoader> {
self.context.module_loader()
}
pub fn load_default_extensions(&mut self) -> Result<(), std::io::Error> {
let exts = vec![DefaultScriptExtension {
script: include_str!("./extensions/00_timers.ts"),
transpile: true,
}];
for ext in exts {
let js_src = if ext.transpile {
self.transpile(&ext.script)?
} else {
ext.script.to_string()
};
let src = Source::from_bytes(js_src.as_bytes());
self.context
.eval(src)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
}
Ok(())
}
pub fn load_extensions(&mut self, ext: Vec<ScriptExtension>) -> Result<(), std::io::Error> {
for e in ext {
let content = std::fs::read_to_string(e.path.as_str())?;
let js_src = if e.transpile {
self.transpile(content.as_str())?
} else {
content
};
let src = Source::from_bytes(js_src.as_bytes());
self.context
.eval(src)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
}
Ok(())
}
pub fn execute(&mut self, code: &str) -> JsResult<JsValue> {
let src = Source::from_bytes(code.as_bytes());
let module = Module::parse(src, None, &mut self.context)?;
let promise = module.load_link_evaluate(&mut self.context);
self.run_event_loop();
match promise.state() {
PromiseState::Pending => Err(JsError::from_opaque(JsValue::String(JsString::from(
"Module failed to execute",
)))),
PromiseState::Fulfilled(value) => Ok(value),
PromiseState::Rejected(err) => Err(JsError::from_opaque(err)),
}
}
pub fn eval(&mut self, code: &str) -> JsResult<JsValue> {
let src = Source::from_bytes(code.as_bytes());
self.context.eval(src)
}
pub fn transpile(&mut self, code: &str) -> Result<String, std::io::Error> {
let transpiled = typescript::transpile_typescript(code);
match transpiled {
Ok(js) => Ok(js),
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
)),
}
}
pub fn run_event_loop(&mut self) {
self.context.run_jobs();
}
}