ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::load
//!
//! Built-in function `Load`.

use crate::{
    Dict,
    Environment,
    FALSE_SYMBOL,
    MODULE_NAME_ENV,
    MODULE_PATH_ENV,
    TRUE_SYMBOL,
    builtin::{DEFAULT_KSL_PATH, FILE_EXT, KSL_PATH_ENV},
    eval::{apply::eval_apply, eval},
    expand_tilde,
    find_define_environment,
    init_environment,
    is_invalid_symbol,
    read_from_environment,
    value::Value,
};

pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    let (target_expr, path_expr, is_force) = match args {
        [t, p] => (t, p, None),
        [t, p, f] => match f {
            op @ Value::Atom(_) if *op == *TRUE_SYMBOL => (t, p, Some(true)),
            op @ Value::Atom(_) if *op == *FALSE_SYMBOL => (t, p, Some(true)),
            e => {
                return Err(std::sync::Arc::from(format!(
                    "Error[ksl::builtin::Load]: Expected a boolean atom, but got {}.",
                    e
                )));
            }
        },
        _ => {
            return Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Load]: Expected 2 or 3 parameters, got {}.",
                args.len()
            )));
        }
    };

    let path_val = eval_apply(path_expr, env.clone())?;
    let path_str = match path_val {
        Value::String(s) => s,
        _ => {
            return Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Load]: Expected path string, got `{}`.",
                path_val
            )));
        }
    };

    let real_path = resolve_path(path_str.as_ref())?;
    let real_path_arc: std::sync::Arc<str> = std::sync::Arc::from(real_path.as_str());

    // check and update cache
    let (name, sym_dict) = {
        let scope = find_define_environment(&env, &MODULE_PATH_ENV).ok_or(std::sync::Arc::from(
            "Error[ksl::builtin::Load]: Uninitialized environment.",
        ))?;
        let lock = scope.store.upgradable_read();
        let paths_val = lock
            .get(MODULE_PATH_ENV.as_ref())
            .ok_or(std::sync::Arc::from(
                "Error[ksl::builtin::Load]: Corrupted environment.",
            ))?
            .clone();
        let cached_result = if is_force.is_some_and(|p| p) {
            None
        } else if let Value::Object(_, d) = paths_val.as_ref() {
            d.get(&real_path_arc).cloned()
        } else {
            return Err(std::sync::Arc::from(
                "Error[ksl::builtin::Load]: Corrupted environment.",
            ));
        };

        match cached_result {
            Some(cached) => match cached.as_ref() {
                Value::Module(n, s) => (n.clone(), s.clone()),
                _ => {
                    return Err(std::sync::Arc::from(format!(
                        "Error[ksl::builtin::Load]: Circular reference to module: `{}`.",
                        real_path_arc
                    )));
                }
            },
            None => {
                let mut writer = parking_lot::RwLockUpgradableReadGuard::upgrade(lock);
                let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
                    Some(Value::Object(_, d)) => (**d).clone(),
                    _ => unreachable!(),
                };
                paths_dict.insert(real_path_arc.clone(), std::sync::Arc::new(Value::Unit));
                let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
                writer.insert(MODULE_PATH_ENV.clone(), paths_obj);
                drop(writer);

                let run_result = run_module(&real_path, &env);
                match run_result {
                    Ok((n, s)) => {
                        let loaded_module = std::sync::Arc::new(Value::Module(n.clone(), s.clone()));
                        let mut writer = scope.store.write();
                        let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
                            Some(Value::Object(_, d)) => (**d).clone(),
                            _ => unreachable!(),
                        };
                        paths_dict.insert(real_path_arc.clone(), loaded_module);
                        let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
                        writer.insert(MODULE_PATH_ENV.clone(), paths_obj);

                        (n, s)
                    }
                    Err(err) => {
                        // do rollback
                        let mut writer = scope.store.write();
                        let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
                            Some(Value::Object(_, d)) => (**d).clone(),
                            _ => unreachable!(),
                        };
                        paths_dict.remove(&real_path_arc);
                        let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
                        writer.insert(MODULE_PATH_ENV.clone(), paths_obj);

                        return Err(err);
                    }
                }
            }
        }
    };

    match target_expr {
        Value::Symbol(mod_sym) => {
            if is_invalid_symbol(mod_sym) {
                Err(std::sync::Arc::from(format!(
                    "Error[ksl::builtin::Load]: Rebinding of symbol `{}` is not permitted.",
                    mod_sym
                )))
            } else {
                let _ = env.store.write().insert(
                    mod_sym.clone(),
                    std::sync::Arc::new(Value::Module(name, sym_dict)),
                );
                Ok(Value::Unit)
            }
        }
        Value::List(symbols) => {
            let mut writer = env.store.write();
            for item in symbols.as_ref() {
                let s = match item {
                    Value::Symbol(s) => s,
                    e => {
                        return Err(std::sync::Arc::from(format!(
                            "Error[ksl::builtin::Load]: Expected a symbol, but got: `{}`.",
                            e,
                        )));
                    }
                };
                let val = sym_dict.get(s).ok_or(std::sync::Arc::from(format!(
                    "Error[ksl::builtin::Load]: Symbol `{}` not found in `{}`.",
                    s, real_path
                )))?;
                let _ = writer.insert(s.clone(), val.clone());
            }
            Ok(Value::Unit)
        }
        e => Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::Load]: ",
                "Expected a symbol or a list of symbols, but got: `{}`."
            ),
            e,
        ))),
    }
}

/// resolve real module path
fn resolve_path(path_str: &str) -> Result<std::string::String, std::sync::Arc<str>> {
    let file_with_ext = format!("{}.{}", path_str, FILE_EXT);
    let path_with_ext = std::path::Path::new(&file_with_ext);
    if path_with_ext.exists() {
        return Ok(file_with_ext);
    }

    let arc_path = std::sync::Arc::from(file_with_ext.as_str());
    if let Ok(expanded) = expand_tilde(arc_path)
        && expanded.exists()
    {
        return Ok(expanded.to_string_lossy().into_owned());
    }

    let ksl_path_env = std::env::var(KSL_PATH_ENV).map_or_else(|_| DEFAULT_KSL_PATH.clone(), std::sync::Arc::from);
    Ok(expand_tilde(ksl_path_env)?
        .join(file_with_ext)
        .to_string_lossy()
        .into_owned())
}

/// eval module and extract symbols
fn run_module(
    real_path: &str,
    parent_env: &Environment,
) -> Result<(std::sync::Arc<str>, std::sync::Arc<Dict>), std::sync::Arc<str>> {
    let local_env = init_environment(None);
    if let Some(paths) = read_from_environment(parent_env, &MODULE_PATH_ENV) {
        let _ = local_env
            .store
            .write()
            .insert(MODULE_PATH_ENV.clone(), paths);
    }

    let file_content = std::fs::read_to_string(real_path).map_err(|_| {
        std::sync::Arc::from(format!(
            "Error[ksl::builtin::Load::run_module]: Unable to read `{}`.",
            real_path
        ))
    })?;
    let eval_result = eval(&file_content, local_env.clone());

    // sync cache
    if let Some(updated_paths) = local_env
        .store
        .read()
        .get(MODULE_PATH_ENV.as_ref())
        .cloned()
        && let Some(scope) = find_define_environment(parent_env, &MODULE_PATH_ENV)
    {
        scope
            .store
            .write()
            .insert(MODULE_PATH_ENV.clone(), updated_paths);
    }
    // deal result
    let _ = eval_result?;

    let reader = local_env.store.read();
    let mod_name = reader
        .get(MODULE_NAME_ENV.as_ref())
        .and_then(|v| match v.as_ref() {
            Value::String(s) => Some(s.clone()),
            _ => None,
        })
        .ok_or_else(|| {
            std::sync::Arc::from(format!(
                "Error[ksl::builtin::Load::run_module]: `{}` is not a module.",
                real_path
            ))
        })?;
    let sym_dict = reader
        .iter()
        .filter(|(k, _)| !k.starts_with('_') && !is_invalid_symbol(k))
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect::<Dict>();

    Ok((mod_name, std::sync::Arc::new(sym_dict)))
}