ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::value
//!
//! Defines all the types and related functions for all the values
//! that are accepted in KSL.

use crate::{Dict, Environment, is_number_eq};

/// Alias for thread object value type.
pub type KSLThread = std::sync::Arc<parking_lot::Mutex<Option<std::thread::JoinHandle<Result<Value, std::sync::Arc<str>>>>>>;

/// Values in KSL.
#[derive(Clone, Debug)]
pub enum Value {
    /// Unit value.
    Unit,
    /// Module name and environment of the module.
    Module(std::sync::Arc<str>, std::sync::Arc<Dict>),
    /// Symbol, which usually indicates the name of the variable.
    Symbol(std::sync::Arc<str>),
    /// Atom value.
    Atom(std::sync::Arc<str>),
    /// String value.
    String(std::sync::Arc<str>),
    /// Number value.
    Number(f64),
    /// List value.
    List(std::sync::Arc<[Value]>),
    /// Function parameter list, function body, and captured environment
    Lambda(Vec<std::sync::Arc<str>>, std::sync::Arc<Value>, Environment),
    /// Built-in function name.
    Builtin(&'static str),
    /// Plug-in function names and corresponding library names and function symbols.
    Plugin(std::sync::Arc<str>, std::sync::Arc<str>),
    /// Object type name and key-value pairs.
    Object(std::sync::Arc<str>, Box<Dict>),
    /// Thread object
    Thread(KSLThread),
    /// Raw object for plugin to store any object.
    NativeObject(
        std::sync::Arc<str>,
        std::sync::Arc<parking_lot::Mutex<dyn std::any::Any + Send>>,
    ),
    /// Function argument list and function body,
    Apply(Vec<Value>, std::sync::Arc<Value>),
}

/// Check if two values are equal.
///
/// Modules and lambdas cannot compare to each other,
/// applies will never compare to other values.
impl std::cmp::PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Value::Unit, Value::Unit) => true,
            (Value::Symbol(sym1), Value::Symbol(sym2)) => sym1 == sym2,
            (Value::Atom(a1), Value::Atom(a2)) => a1 == a2,
            (Value::String(s1), Value::String(s2)) => s1 == s2,
            (Value::Number(n1), Value::Number(n2)) => is_number_eq(*n1, *n2),
            (Value::List(v1), Value::List(v2)) => {
                std::sync::Arc::ptr_eq(v1, v2) || v1.len() == v2.len() && v1.iter().zip(v2.iter()).all(|(e1, e2)| e1 == e2)
            }
            (Value::Builtin(n1), Value::Builtin(n2)) => n1 == n2,
            (Value::Object(typ1, dict1), Value::Object(typ2, dict2)) => typ1 == typ2 && dict1 == dict2,
            _ => false,
        }
    }
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Unit => write!(f, "()"),
            Value::Module(module_name, env) => write!(
                f,
                "Module[{}]{{{}}}",
                module_name,
                env.keys()
                    .map(|k| k.as_ref())
                    .collect::<Vec<&str>>()
                    .join(", ")
            ),
            Value::Symbol(sym) => write!(f, "{sym}"),
            Value::Atom(a) => write!(f, "#{a}"),
            Value::String(s) => write!(f, "{s:?}"),
            Value::Number(n) => write!(f, "{}", fish_printf::sprintf!("%.15g", n)),
            Value::List(values) => write!(
                f,
                "{{{}}}",
                values
                    .iter()
                    .map(|v| v.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
            Value::Lambda(params, _, _) => write!(
                f,
                "λ({}) => <lambda>",
                params
                    .iter()
                    .map(|k| k.as_ref())
                    .collect::<Vec<&str>>()
                    .join(", ")
            ),
            Value::Builtin(name) => write!(f, "<<{name}>>"),
            Value::Plugin(name, libfunc) => write!(f, "Plugin[{libfunc}]({name})"),
            Value::Thread(_) => write!(f, "<Thread>"),
            Value::Object(type_name, dict) => write!(
                f,
                "Object[{}]{{{}}}",
                type_name,
                dict.keys()
                    .map(|k| k.as_ref())
                    .collect::<Vec<&str>>()
                    .join(", ")
            ),
            Value::NativeObject(type_name, _) => write!(f, "Native[{type_name}]"),
            Value::Apply(arguments, func) => write!(
                f,
                "{}[{}]",
                func,
                arguments
                    .iter()
                    .map(|v| v.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
        }
    }
}

/// Generate native object manipulations.
#[macro_export]
macro_rules! impl_native_object {
    (
        // native object info
        rust_type: $rust_type:ty,
        type_name_const: $const_name:ident,
        type_name_str: $type_str:literal,
        // with helper
        with_fn: {
            name: $with_fn_name:ident,
            error_prefix: $with_err_prefix:literal
        }
        // check function
        $(,
        is_fn: {
            $(#[$is_meta:meta])*
            name: $is_fn_name:ident,
            func_name: $is_func_str:literal,
            error_prefix: $is_err_prefix:literal
        })?
        // drop function
        $(,
        drop_fn: {
            $(#[$drop_meta:meta])*
            name: $drop_fn_name:ident,
            func_name: $drop_func_str:literal,
            error_prefix: $drop_err_prefix:literal
        })?
    ) => {
        // generate type name
        pub static $const_name: ::std::sync::LazyLock<::std::sync::Arc<str>> =
            ::std::sync::LazyLock::new(|| ::std::sync::Arc::from($type_str));
        // generate with helper
        pub fn $with_fn_name<F, R>(val: &$crate::value::Value, func: F) -> Result<R, ::std::sync::Arc<str>>
        where
            F: FnOnce(&$rust_type) -> Result<R, ::std::sync::Arc<str>>,
        {
            match val {
                $crate::value::Value::NativeObject(type_name, data) if type_name.as_ref() == $const_name.as_ref() => {
                    let guard = data.lock();
                    if let Some(obj) = guard.downcast_ref::<$rust_type>() {
                        func(obj)
                    } else {
                        Err(::std::sync::Arc::from(concat!(
                            "Error[", $with_err_prefix, "::", stringify!($with_fn_name), "]: ",
                            "Native object is not a ", $type_str, "."
                        )))
                    }
                }
                e => Err(::std::sync::Arc::from(format!(
                    concat!(
                        "Error[", $with_err_prefix, "::", stringify!($with_fn_name), "]: ",
                        "Expected a ", $type_str, ", but got `{}`."
                    ),
                    e
                ))),
            }
        }
        // check function
        $(
            $(#[$is_meta])*
            pub fn $is_fn_name(args: &[$crate::value::Value], env: $crate::Environment)
                -> Result<$crate::value::Value, ::std::sync::Arc<str>> {
                if let [arg] = args {
                    match $crate::eval::apply::eval_apply(arg, env)? {
                        $crate::value::Value::NativeObject(type_name, _) => Ok(if type_name.as_ref() == $const_name.as_ref() {
                            $crate::TRUE_SYMBOL.clone()
                        } else {
                            $crate::FALSE_SYMBOL.clone()
                        }),
                        e => Err(::std::sync::Arc::from(format!(
                            concat!(
                                "Error[", $is_err_prefix, "::", $is_func_str, "]: ",
                                "Unexpected value: `{}`."),
                            e
                        ))),
                    }
                } else {
                    Err(::std::sync::Arc::from(format!(
                        concat!(
                            "Error[", $is_err_prefix, "::", $is_func_str, "]: ",
                            "Expected 1 parameter, but {} were passed."
                        ),
                        args.len()
                    )))
                }
            }
        )?
        // drop function
        $(
            $(#[$drop_meta])*
            pub fn $drop_fn_name(args: &[$crate::value::Value], env: $crate::Environment)
                -> Result<$crate::value::Value, ::std::sync::Arc<str>> {
                if let [sym_expr] = args {
                    if let $crate::value::Value::Symbol(sym) = sym_expr {
                        let defined_env = $crate::find_define_environment(&env, sym)
                            .ok_or(::std::sync::Arc::from(format!(
                                concat!("Error[", $drop_err_prefix, "::", $drop_func_str, "]: ",
                                    "Unknown symbol `{}`."),
                                sym
                            )))
                            .and_then(|scope| {
                                let Some(val) = scope.store.read().get(sym).cloned() else {
                                    unreachable!();
                                };

                                if matches!(
                                    val.as_ref(),
                                    $crate::value::Value::NativeObject(type_name, _)
                                        if type_name.as_ref() == $const_name.as_ref()
                                ) {
                                    Ok(scope)
                                } else {
                                    Err(::std::sync::Arc::from(format!(
                                        concat!("Error[", $drop_err_prefix, "::", $drop_func_str, "]: ",
                                            "Expect a ", $type_str, ", but got: `{}`."),
                                        val
                                    )))
                                }
                            })?;
                        // update store
                        let _ = defined_env
                            .store
                            .write()
                            .insert(sym.clone(), ::std::sync::Arc::new($crate::value::Value::Unit));
                        Ok($crate::value::Value::Unit)
                    } else {
                        Err(::std::sync::Arc::from(format!(
                            concat!(
                                "Error[", $drop_err_prefix, "::", $drop_func_str, "]: ",
                                "Expected a Symbol, but got `{}`."),
                            sym_expr
                        )))
                    }
                } else {
                    Err(::std::sync::Arc::from(format!(
                        concat!(
                            "Error[", $drop_err_prefix, "::", $drop_func_str, "]: ",
                            "Expected 1 parameter, but {} were passed."
                        ),
                        args.len()
                    )))
                }
            }
        )?
    };
}