use crate::prelude::*;
use std::borrow::Cow;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct YarnFnRegistry(pub(crate) InnerRegistry);
type InnerRegistry = HashMap<Cow<'static, str>, Box<dyn UntypedYarnFn>>;
impl Extend<<InnerRegistry as IntoIterator>::Item> for YarnFnRegistry {
fn extend<T: IntoIterator<Item = <InnerRegistry as IntoIterator>::Item>>(&mut self, iter: T) {
self.0.extend(iter);
}
}
impl IntoIterator for YarnFnRegistry {
type Item = <InnerRegistry as IntoIterator>::Item;
type IntoIter = <InnerRegistry as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl YarnFnRegistry {
pub(crate) fn register_function<Marker, F>(
&mut self,
name: impl Into<Cow<'static, str>>,
function: F,
) -> &mut Self
where
Marker: 'static,
F: YarnFn<Marker> + 'static + Clone,
F::Out: IntoYarnValueFromNonYarnValue + 'static + Clone,
{
let name = name.into();
let wrapped = YarnFnWrapper::from(function);
self.0.insert(name, Box::new(wrapped));
self
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &(dyn UntypedYarnFn))> {
self.0
.iter()
.map(|(key, value)| (key.as_ref(), value.as_ref()))
}
pub(crate) fn add_boxed(
&mut self,
name: impl Into<Cow<'static, str>>,
function: Box<dyn UntypedYarnFn>,
) -> &mut Self {
let name = name.into();
self.0.insert(name, function);
self
}
pub(crate) fn contains_function(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub(crate) fn get(&self, name: &str) -> Option<&(dyn UntypedYarnFn)> {
self.0.get(name).map(|f| f.as_ref())
}
pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(|key| key.as_ref())
}
pub(crate) fn functions(&self) -> impl Iterator<Item = &(dyn UntypedYarnFn)> {
self.0.values().map(|value| value.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_add_fn_with_no_args() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test", || true);
}
#[test]
fn can_add_fn_with_one_arg() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test", |a: f32| a);
}
#[test]
fn can_call_fn_with_no_args() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test", || true);
let function = functions.get("test").unwrap();
let result: bool = function.call(vec![]).try_into().unwrap();
assert!(result);
}
#[test]
fn can_call_fn_with_one_arg() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test", |a: f32| a);
let function = functions.get("test").unwrap();
let result: f32 = function.call(to_function_params([1.0])).try_into().unwrap();
assert_eq!(result, 1.0);
}
#[test]
fn can_add_multiple_fns() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test1", || true);
functions.register_function("test2", |a: f32| a);
}
#[test]
fn can_call_multiple_fns() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test1", || true);
functions.register_function("test2", |a: f32| a);
let function1 = functions.get("test1").unwrap();
let function2 = functions.get("test2").unwrap();
let result1: bool = function1.call(vec![]).try_into().unwrap();
let result2: f32 = function2
.call(to_function_params([1.0]))
.try_into()
.unwrap();
assert!(result1);
assert_eq!(result2, 1.0);
}
#[test]
fn can_call_multiple_fns_with_many_params() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test1", || true);
functions.register_function("test2", |a: f32, b: f32| a + b);
functions.register_function("test3", |a: f32, b: f32, c: f32| a + b * c);
functions.register_function(
"test4",
|a: String, b: String, c: String, d: bool, e: f32| format!("{}{}{}{}{}", a, b, c, d, e),
);
let function1 = functions.get("test1").unwrap();
let function2 = functions.get("test2").unwrap();
let function3 = functions.get("test3").unwrap();
let function4 = functions.get("test4").unwrap();
let result1: bool = function1.call(vec![]).try_into().unwrap();
let result2: f32 = function2
.call(to_function_params([1.0, 2.0]))
.try_into()
.unwrap();
let result3: f32 = function3
.call(to_function_params([1.0, 2.0, 3.0]))
.try_into()
.unwrap();
let result4: String = function4
.call(to_function_params([
YarnValue::from("a"),
"b".into(),
"c".into(),
true.into(),
1.0.into(),
]))
.into();
assert!(result1);
assert_eq!(result2, 3.0);
assert_eq!(result3, 7.0);
assert_eq!(result4, "abctrue1".to_string());
}
fn to_function_params(
params: impl IntoIterator<Item = impl Into<YarnValue>>,
) -> Vec<YarnValue> {
params.into_iter().map(Into::into).collect()
}
#[test]
fn debug_prints_signature() {
let mut functions = YarnFnRegistry::default();
functions.register_function("test", |a: f32, b: f32| a + b);
let debug_string = format!("{:?}", functions);
let element_start = debug_string.find('{').unwrap();
let element_end = element_start + debug_string[element_start + 1..].find('{').unwrap();
let element = &debug_string[element_start..element_end];
assert_eq!(element, "{\"test\": fn(f32, f32) -> f32");
}
}