ksl 0.1.7

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

use crate::{
    Environment,
    MODULE_NAME_ENV,
    MODULE_PATH_ENV,
    builtin::{DEFAULT_KSL_PATH, FILE_EXT, KSL_PATH_ENV},
    eval::{apply::eval_apply, eval},
    expand_tilde,
    init_environment,
    value::Value,
};

pub(crate) fn builtin(args: &[Value], env: &Environment) -> Option<(Value, Environment)> {
    let mut local_environment = Environment::new();
    if let [Value::Identity(name), path_string] = &args[..] {
        if env.contains_key(name) {
            eprintln!(
                concat!(
                    "Error[ksl::builtin::load]: ",
                    "Symbol `{}` cannot be rebound."
                ),
                name
            );
            None
        } else {
            match eval_apply(path_string, env) {
                Some((Value::String(path), _)) => {
                    let path_with_ext = format!("{}.{}", path, FILE_EXT);
                    let real_path = if let Ok(true) = std::fs::exists(&path_with_ext) {
                        path_with_ext
                    } else {
                        let ksl_path = match std::env::var(KSL_PATH_ENV) {
                            Ok(p) => p,
                            Err(_) => String::from(DEFAULT_KSL_PATH),
                        };
                        if let Some(ksl_path) = expand_tilde(ksl_path) {
                            format!("{}/{}", ksl_path, path_with_ext)
                        } else {
                            return None;
                        }
                    };
                    if let Some(Value::List(module_paths)) = env.get(MODULE_PATH_ENV) {
                        if module_paths.contains(&Value::String(real_path.clone())) {
                            eprintln!(concat!(
                                "Error[ksl::builtin::load]: ",
                                "Module has already been loaded."
                            ));
                            None
                        } else {
                            let mut local_env = init_environment();
                            let mut currnet_paths = module_paths.clone();
                            currnet_paths.push(Value::String(real_path.clone()));
                            let _ = local_env.insert(
                                String::from(MODULE_PATH_ENV),
                                Value::List(currnet_paths.clone()),
                            );
                            let _ = local_environment.insert(String::from(MODULE_PATH_ENV), Value::List(currnet_paths));
                            match std::fs::File::options()
                                .read(true)
                                .open(&real_path)
                                .and_then(|f| {
                                    let mut buffer = String::new();
                                    std::io::Read::read_to_string(&mut std::io::BufReader::new(f), &mut buffer).map(|_| buffer)
                                }) {
                                Ok(load_file) => match eval(&load_file, &local_env) {
                                    Some((_, mut module_env)) => {
                                        if let Some(Value::String(module_name)) = module_env.remove(MODULE_NAME_ENV) {
                                            let _ =
                                                local_environment.insert(name.clone(), Value::Module(module_name, module_env));
                                            Some((Value::Unit, local_environment))
                                        } else {
                                            eprintln!(
                                                concat!("Error[ksl::builtin::load]: ", "`{}` is not a module."),
                                                real_path
                                            );
                                            None
                                        }
                                    }
                                    None => {
                                        eprintln!(
                                            concat!("Error[ksl::builtin::load]: ", "Failed to load module `{}`."),
                                            name
                                        );
                                        None
                                    }
                                },
                                Err(_) => {
                                    eprintln!(
                                        concat!(
                                            "Error[ksl::builtin::load]: ",
                                            "Unable to read contents of file at `{}`."
                                        ),
                                        real_path
                                    );
                                    None
                                }
                            }
                        }
                    } else {
                        eprintln!(concat!(
                            "Error[ksl::builtin::load]: ",
                            "Uninitialized environment."
                        ));
                        None
                    }
                }
                Some((e, _)) => {
                    eprintln!(
                        concat!(
                            "Error[ksl::builtin::load]: ",
                            "Expected a string, but got `{:?}`."
                        ),
                        e
                    );
                    None
                }
                None => {
                    eprintln!(
                        concat!(
                            "Error[ksl::builtin::load]: ",
                            "Cannot evaluate expression {:?}."
                        ),
                        path_string
                    );
                    None
                }
            }
        }
    } else {
        eprintln!(
            concat!(
                "Error[ksl::builtin::load]: ",
                "Only accepts a symbol ",
                "and a file path without suffix as arguments, ",
                "but got {:?}.",
            ),
            args
        );
        None
    }
}