use crate::{
JSError, Value,
core::{ClosureData, DestructuringElement, Expr, Statement, StatementKind, obj_get_key_value, obj_set_key_value},
};
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
pub fn load_module(module_name: &str, base_path: Option<&str>) -> Result<Value, JSError> {
let module_exports = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
if module_name == "math" {
let pi = Value::Number(std::f64::consts::PI);
let e = Value::Number(std::f64::consts::E);
obj_set_key_value(&module_exports, &"PI".into(), pi)?;
obj_set_key_value(&module_exports, &"E".into(), e)?;
let identity_func = Value::Closure(Rc::new(ClosureData::new(
&[DestructuringElement::Variable("x".to_string(), None)],
&[Statement {
kind: StatementKind::Return(Some(Expr::Var("x".to_string(), None, None))),
line: 0,
column: 0,
}],
&module_exports,
None,
)));
obj_set_key_value(&module_exports, &"identity".into(), identity_func)?;
} else if module_name == "console" {
let log_func = Value::Function("console.log".to_string());
obj_set_key_value(&module_exports, &"log".into(), log_func)?;
} else if module_name == "std" {
let std_obj = crate::js_std::make_std_object()?;
return Ok(Value::Object(std_obj));
} else if module_name == "os" {
let os_obj = crate::js_os::make_os_object()?;
return Ok(Value::Object(os_obj));
} else {
match load_module_from_file(module_name, base_path) {
Ok(loaded_module) => return Ok(loaded_module),
Err(_) => {
log::debug!("Failed to load module '{module_name}' from file, returning empty module");
}
}
}
Ok(Value::Object(module_exports))
}
fn load_module_from_file(module_name: &str, base_path: Option<&str>) -> Result<Value, JSError> {
let module_path = resolve_module_path(module_name, base_path)?;
let content = crate::core::read_script_file(&module_path)?;
execute_module(&content, &module_path)
}
fn resolve_module_path(module_name: &str, base_path: Option<&str>) -> Result<String, JSError> {
let path = Path::new(module_name);
if path.is_absolute() || module_name.starts_with("./") || module_name.starts_with("../") {
let mut full_path = if let Some(base) = base_path {
Path::new(base).parent().unwrap_or(Path::new(".")).join(module_name)
} else {
std::env::current_dir()
.map_err(|e| raise_eval_error!(format!("Failed to get current directory: {e}")))?
.join(module_name)
};
if full_path.extension().is_none() {
full_path.set_extension("js");
}
match full_path.canonicalize() {
Ok(canonical) => Ok(canonical.to_string_lossy().to_string()),
Err(_) => Err(raise_eval_error!(format!("Module file not found: {}", full_path.display()))),
}
} else {
let mut full_path = Path::new(module_name).to_path_buf();
if full_path.extension().is_none() {
full_path.set_extension("js");
}
match full_path.canonicalize() {
Ok(canonical) => Ok(canonical.to_string_lossy().to_string()),
Err(_) => Err(raise_eval_error!(format!("Module file not found: {}", full_path.display()))),
}
}
}
fn execute_module(content: &str, module_path: &str) -> Result<Value, JSError> {
let module_exports = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
let env = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
env.borrow_mut().is_function_scope = true;
let val = Value::String(crate::unicode::utf8_to_utf16(module_path));
obj_set_key_value(&env, &"__script_name".into(), val)?;
env.borrow_mut().insert(
crate::core::PropertyKey::String("exports".to_string()),
Rc::new(RefCell::new(Value::Object(module_exports.clone()))),
);
let module_obj = Rc::new(RefCell::new(crate::core::JSObjectData::new()));
module_obj.borrow_mut().insert(
crate::core::PropertyKey::String("exports".to_string()),
Rc::new(RefCell::new(Value::Object(module_exports.clone()))),
);
env.borrow_mut().insert(
crate::core::PropertyKey::String("module".to_string()),
Rc::new(RefCell::new(Value::Object(module_obj.clone()))),
);
crate::core::initialize_global_constructors(&env)?;
crate::core::obj_set_key_value(&env, &"globalThis".into(), crate::core::Value::Object(env.clone()))?;
let mut tokens = crate::core::tokenize(content)?;
let statements = crate::core::parse_statements(&mut tokens)?;
crate::core::evaluate_statements(&env, &statements)?;
log::trace!("Module executed, exports keys:");
for key in module_exports.borrow().keys() {
log::trace!(" - {}", key);
}
if let Some(module_exports_val) = obj_get_key_value(&module_obj, &"exports".into())? {
match &*module_exports_val.borrow() {
Value::Object(obj) if Rc::ptr_eq(obj, &module_exports) => {
Ok(Value::Object(module_exports))
}
other_value => {
Ok(other_value.clone())
}
}
} else {
Ok(Value::Object(module_exports))
}
}
pub fn import_from_module(module_value: &Value, specifier: &str) -> Result<Value, JSError> {
match module_value {
Value::Object(obj) => match obj_get_key_value(obj, &specifier.into())? {
Some(val) => Ok(val.borrow().clone()),
None => Err(raise_eval_error!(format!("Export '{}' not found in module", specifier))),
},
_ => Err(raise_eval_error!("Module is not an object")),
}
}
#[allow(dead_code)]
pub fn get_module_default_export(module_value: &Value) -> Value {
match module_value {
Value::Object(_) => {
match import_from_module(module_value, "default") {
Ok(default_value) => default_value,
Err(_) => module_value.clone(),
}
}
_ => {
module_value.clone()
}
}
}