use crate::{Dict, Environment, is_number_eq};
pub type KSLThread = std::sync::Arc<parking_lot::Mutex<Option<std::thread::JoinHandle<Result<Value, std::sync::Arc<str>>>>>>;
#[derive(Clone, Debug)]
pub enum Value {
Unit,
Module(std::sync::Arc<str>, std::sync::Arc<Dict>),
Symbol(std::sync::Arc<str>),
Atom(std::sync::Arc<str>),
String(std::sync::Arc<str>),
Number(f64),
List(std::sync::Arc<[Value]>),
Lambda(Vec<std::sync::Arc<str>>, std::sync::Arc<Value>, Environment),
Builtin(&'static str),
Plugin(std::sync::Arc<str>, std::sync::Arc<str>),
Object(std::sync::Arc<str>, Box<Dict>),
Thread(KSLThread),
NativeObject(
std::sync::Arc<str>,
std::sync::Arc<parking_lot::Mutex<dyn std::any::Any + Send>>,
),
Apply(Vec<Value>, std::sync::Arc<Value>),
}
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(", ")
),
}
}
}
#[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_fn: {
$(#[$drop_meta:meta])*
name: $drop_fn_name:ident,
func_name: $drop_func_str:literal,
error_prefix: $drop_err_prefix:literal
})?
) => {
pub static $const_name: ::std::sync::LazyLock<::std::sync::Arc<str>> =
::std::sync::LazyLock::new(|| ::std::sync::Arc::from($type_str));
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()
)))
}
}
)?
};
}