use crate::core::{error::ScriptError, value::ScriptValue, ScriptingEnvironment};
use rusty_v8 as v8;
use std::{collections::HashMap, sync::Once};
static PLATFORM_INIT: Once = Once::new();
fn ensure_platform_init() {
PLATFORM_INIT.call_once(|| {
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
});
}
fn trycatch_scope_to_scripterror(
tc_scope: &mut v8::TryCatch<v8::HandleScope>,
is_compile_step: bool,
) -> ScriptError {
let exception = match tc_scope.exception() {
Some(e) => e,
None => {
return ScriptError::CastError {
type_from: "Option<v8::Exception>",
type_to: "v8::Value",
}
}
};
let msg = v8::Exception::create_message(tc_scope, exception);
if is_compile_step {
ScriptError::CompileError(String::from(
msg.get(tc_scope).to_rust_string_lossy(tc_scope),
))
} else {
ScriptError::RuntimeError(String::from(
msg.get(tc_scope).to_rust_string_lossy(tc_scope),
))
}
}
fn val_to_scriptvalue(
scope: &mut v8::HandleScope,
value: &v8::Local<v8::Value>,
) -> Result<ScriptValue, ScriptError> {
let value = value.to_string(scope).ok_or(ScriptError::CastError {
type_from: "v8::Value",
type_to: "v8::String",
})?;
let json_str = value.to_rust_string_lossy(scope);
Ok(serde_json::from_str(&json_str)
.map_err(|e| ScriptError::SerializationError(e.to_string()))?)
}
struct V8ScriptingState {
handlers: HashMap<String, Box<dyn FnMut(&str) -> Result<String, String>>>,
}
fn internal_core_call_to_rust_receiver(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) -> Result<(), String> {
assert_eq!(args.length(), 2);
let handler_name = args
.get(0)
.to_string(scope)
.ok_or("Can't get first argument as string")?
.to_rust_string_lossy(scope);
let handler_data = args
.get(1)
.to_string(scope)
.ok_or("Can't get second argument as string")?
.to_rust_string_lossy(scope);
let handler_result = {
let mut state = scope
.get_slot_mut::<V8ScriptingState>()
.ok_or("Can't acquire V8ScriptingState")?;
let handler_closure = state
.handlers
.get_mut(&handler_name)
.ok_or(format!("Can't get unregistered handler: {}", &handler_name))?;
handler_closure(&handler_data)?
};
let handler_result = v8::String::new(scope, &handler_result)
.ok_or("Can't convert resulting value into string")?;
rv.set(handler_result.into());
Ok(())
}
fn core_call_to_rust_receiver(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
rv: v8::ReturnValue,
) {
match internal_core_call_to_rust_receiver(scope, args, rv) {
Err(err_str) => {
let err_str = v8::String::new(scope, &err_str).unwrap();
let exception = v8::Exception::error(scope, err_str);
scope.throw_exception(exception);
}
Ok(_) => {}
}
}
pub struct V8ScriptingEnvironment {
isolate: v8::OwnedIsolate,
global_context: v8::Global<v8::Context>,
}
impl V8ScriptingEnvironment {
pub fn new() -> V8ScriptingEnvironment {
ensure_platform_init();
let mut isolate = v8::Isolate::new(Default::default());
let global_context;
{
let scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
global_context = v8::Global::new(scope, context);
let bs_src = format!(
"{}\n{}",
include_str!("./v8_bootstrap.js"),
include_str!("../js/shared_bootstrap.js")
);
let bs_src = v8::String::new(scope, &bs_src).unwrap();
let bs_script = v8::Script::compile(scope, bs_src, None).unwrap();
bs_script.run(scope).unwrap();
let scriptit_str = v8::String::new(scope, "ScriptIt").unwrap();
let core_str = v8::String::new(scope, "core").unwrap();
let call_to_rust_str = v8::String::new(scope, "callToRust").unwrap();
let call_to_rust_fn = v8::FunctionTemplate::new(scope, core_call_to_rust_receiver);
let call_to_rust_fn = call_to_rust_fn.get_function(scope).unwrap();
global_context
.get(scope)
.global(scope)
.get(scope, scriptit_str.into())
.unwrap()
.to_object(scope)
.unwrap()
.get(scope, core_str.into())
.unwrap()
.to_object(scope)
.unwrap()
.set(scope, call_to_rust_str.into(), call_to_rust_fn.into());
};
isolate.set_slot::<V8ScriptingState>(V8ScriptingState {
handlers: HashMap::new(),
});
V8ScriptingEnvironment {
isolate,
global_context,
}
}
}
impl ScriptingEnvironment for V8ScriptingEnvironment {
fn eval_expression(&mut self, source: &str) -> Result<ScriptValue, ScriptError> {
let source = format!("JSON.stringify({})", source);
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
let source = v8::String::new(scope, &source).ok_or(ScriptError::CastError {
type_from: "&str",
type_to: "v8::String",
})?;
let tc_scope = &mut v8::TryCatch::new(scope);
let script = match v8::Script::compile(tc_scope, source, None) {
Some(script) => script,
None => {
return Err(trycatch_scope_to_scripterror(tc_scope, true));
}
};
match script.run(tc_scope) {
Some(value) => val_to_scriptvalue(tc_scope, &value),
None => {
return Err(trycatch_scope_to_scripterror(tc_scope, false));
}
}
}
fn run(&mut self, source: &str) -> Result<(), ScriptError> {
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
let source = v8::String::new(scope, &source).ok_or(ScriptError::CastError {
type_from: "&str",
type_to: "v8::String",
})?;
let tc_scope = &mut v8::TryCatch::new(scope);
let script = match v8::Script::compile(tc_scope, source, None) {
Some(script) => script,
None => {
return Err(trycatch_scope_to_scripterror(tc_scope, true));
}
};
match script.run(tc_scope) {
Some(_) => Ok(()),
None => {
return Err(trycatch_scope_to_scripterror(tc_scope, false));
}
}
}
fn register_core_handler(
&mut self,
handler_name: &str,
handler_closure: Box<dyn FnMut(&str) -> Result<String, String>>,
) {
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
scope
.get_slot_mut::<V8ScriptingState>()
.unwrap()
.handlers
.insert(handler_name.to_string(), handler_closure);
}
}
pub type PlatformScriptingEnvironment = V8ScriptingEnvironment;