use oxide_core::vm::compile::{CompileError, CompileKind, CompileRequest, Compiler};
use oxide_core::vm::module::ModuleSource;
use crate::engine::api::error::{Error, ErrorKind};
use crate::engine::code::debug::DebugInfoMode;
use crate::engine::compiler::register::{LowerError, LoweredScript, lower_owned_script_into};
use crate::engine::compiler::{
EvalCompileContext, compile_unlinked_eval_source_with_filename,
compile_unlinked_script_bytes_with_filename, compile_unlinked_script_source_with_filename,
};
use crate::source::SourceText;
pub(crate) fn core_error_kind(error: &Error) -> oxide_core::ErrorKind {
match error.kind() {
ErrorKind::Syntax => oxide_core::ErrorKind::Syntax,
ErrorKind::Range => oxide_core::ErrorKind::Range,
ErrorKind::Reference => oxide_core::ErrorKind::Reference,
ErrorKind::Type => oxide_core::ErrorKind::Type,
_ => oxide_core::ErrorKind::Internal,
}
}
pub(crate) fn syntax_location(error: &Error, source: &[u8]) -> Option<(u32, u32)> {
if error.kind() != ErrorKind::Syntax {
return None;
}
let span = error.span()?;
crate::source::coordinates::QuickJsSourceLocator::from_bytes(source)
.locate_byte_offset(span.start.byte_offset)
.ok()?
.one_based()
}
pub(crate) fn front_compile_error(error: &Error, source: &[u8], filename: &str) -> CompileError {
let failure = CompileError::new(core_error_kind(error), error.message().to_owned());
match syntax_location(error, source) {
Some((line, column)) => failure.at(filename, line, column),
None => failure,
}
}
pub(crate) fn lower_compile_error(error: LowerError) -> CompileError {
match error {
LowerError::Syntax(message) => CompileError::new(oxide_core::ErrorKind::Syntax, message),
other => CompileError::new(oxide_core::ErrorKind::Internal, other.to_string()),
}
}
#[derive(Debug)]
pub(crate) enum ScriptCompileError {
Front(Error),
Lower(LowerError),
}
pub(crate) fn compile_script(
compiler: &mut Compiler<'_>,
source: &[u8],
filename: &str,
debug_info: DebugInfoMode,
) -> Result<LoweredScript, ScriptCompileError> {
let script = compile_unlinked_script_bytes_with_filename(source, filename, debug_info)
.map_err(ScriptCompileError::Front)?;
lower_owned_script_into(compiler, script).map_err(ScriptCompileError::Lower)
}
fn eval_debug_info() -> DebugInfoMode {
super::runtime::current_debug_info().unwrap_or(DebugInfoMode::Full)
}
pub(crate) fn compile_for_core(
compiler: &mut Compiler<'_>,
request: &CompileRequest<'_>,
) -> Result<oxide_core::ObjectId, CompileError> {
let source = SourceText::try_from_utf16(request.source.iter().copied())
.map_err(|error| CompileError::new(oxide_core::ErrorKind::Range, error.to_string()))?;
let debug_info = eval_debug_info();
let compiled = match request.kind {
CompileKind::Script => {
compile_unlinked_script_source_with_filename(&source, request.filename, debug_info)
}
CompileKind::IndirectEval => compile_unlinked_eval_source_with_filename(
&source,
request.filename,
debug_info,
EvalCompileContext::indirect(),
),
};
let script = compiled
.map_err(|error| front_compile_error(&error, source.raw_bytes(), request.filename))?;
let lowered = lower_owned_script_into(compiler, script).map_err(lower_compile_error)?;
Ok(lowered.function)
}
pub(crate) fn normalize_module_name(referrer: &str, specifier: &str) -> String {
if !specifier.starts_with('.') {
return specifier.to_owned();
}
let mut directory = match referrer.rfind('/') {
Some(slash) => referrer[..slash].to_owned(),
None => String::new(),
};
let mut rest = specifier;
loop {
if let Some(next) = rest.strip_prefix("./") {
rest = next;
} else if let Some(next) = rest.strip_prefix("../") {
if directory.is_empty() {
break;
}
let last = directory.rfind('/').map_or(0, |slash| slash + 1);
let component = &directory[last..];
if component == "." || component == ".." {
break;
}
directory.truncate(last.saturating_sub(1));
rest = next;
} else {
break;
}
}
if directory.is_empty() {
rest.to_owned()
} else {
format!("{directory}/{rest}")
}
}
pub(crate) enum ModuleCompileError {
Front(Error),
Core(CompileError),
}
pub(crate) fn compile_module(
compiler: &mut Compiler<'_>,
name: &str,
source: &[u8],
debug_info: DebugInfoMode,
) -> Result<(ModuleSource, oxide_core::CodeUnitId), ModuleCompileError> {
let failure = |kind: oxide_core::ErrorKind, message: String| {
ModuleCompileError::Core(CompileError::new(kind, message))
};
let module_name = crate::engine::value::JsString::try_from_utf8(name)
.map_err(|error| failure(oxide_core::ErrorKind::Internal, error.to_string()))?;
let module = crate::engine::compiler::compile_unlinked_module_bytes_with_name(
source,
module_name,
debug_info,
)
.map_err(ModuleCompileError::Front)?;
crate::engine::compiler::register::lower_module_tree_into(compiler, &module)
.map_err(|error| ModuleCompileError::Core(lower_compile_error(error)))
}
#[cfg(feature = "test262-host")]
pub(crate) fn run_agent_script(
runtime: &mut oxide_core::Runtime,
source: &str,
) -> Result<(), String> {
let lowered = compile_script(
&mut runtime.compiler(),
source.as_bytes(),
"<agent>",
DebugInfoMode::Full,
)
.map_err(|error| match error {
ScriptCompileError::Front(error) => error.to_string(),
ScriptCompileError::Lower(error) => error.to_string(),
})?;
let function = oxide_core::Value::object(lowered.function);
let handle = runtime.hold(function);
let mut result = runtime.run_script(lowered.function);
runtime.release(handle);
if result.is_ok() {
result = runtime.run_jobs().map(|()| oxide_core::Value::UNDEFINED);
}
result.map(drop).map_err(|thrown| {
runtime
.display(thrown.0)
.unwrap_or_else(|_| "exception while printing the exception".into())
})
}