use std::collections::HashMap;
use crate::model::{Value, Type};
use super::context::Context;
use super::error::Error;
pub struct Dispatcher<'d, 'ctx: 'd> {
context: &'d mut Context<'ctx>,
targets: HashMap<
Vec<Type>, Box<dyn FnOnce(&mut Context, &[&Value]) -> Result<Value, Error> + 'd>
>,
}
impl<'d, 'ctx> Dispatcher<'d, 'ctx> {
pub fn new(ctx: &'d mut Context<'ctx>) -> Self {
Self { context: ctx, targets: Default::default() }
}
pub fn add(
&mut self, types: impl IntoIterator<Item=Type>,
f: impl FnOnce(&mut Context, &[&Value]) -> Result<Value, Error> + 'd,
) -> &mut Self {
let mut duplicate = false;
let entry = self.targets
.entry(types.into_iter().collect())
.and_modify(|_| { duplicate = true; });
if duplicate {
panic!("duplicate definition for {:?}", entry.key());
}
entry.or_insert(Box::new(f));
self
}
pub fn with(
mut self, types: impl IntoIterator<Item=Type>,
f: impl FnOnce(&mut Context, &[&Value]) -> Result<Value, Error> + 'd,
) -> Self {
self.add(types, f);
self
}
pub fn dispatch<'v>(self, args: impl IntoIterator<Item=&'v Value>) -> Result<Value, Error> {
let Dispatcher { context, mut targets } = self;
let args: Vec<_> = args.into_iter().collect();
let sig: Vec<_> = args.iter().map(|v| v.ty()).collect();
match targets.remove(&sig) {
Some(impl_func) => impl_func(context, &args),
None => Err(Error::Type {
expected: targets.into_keys().collect(),
actual: sig,
}),
}
}
}
macro_rules! dispatcher_with {
(
($dispatcher:expr => $ctx_param:tt; $( $param:ident : $ty:ident ),*) =>
$impl:expr
) => {
$dispatcher = $dispatcher.with([ $(Type::$ty),* ], move |$ctx_param, args| {
let &[ $( &Value::$ty($param) ),* ] = args else { unreachable!() };
$impl
})
};
}
macro_rules! dispatcher_with_ref {
(
($dispatcher:expr => $ctx_param:tt; $( $param:ident : $ty:ident ),*) =>
$impl:expr
) => {
$dispatcher = $dispatcher.with([ $(Type::$ty),* ], move |$ctx_param, args| {
let &[ $( &Value::$ty(ref $param) ),* ] = args else { unreachable!() };
$impl
})
};
}
macro_rules! dispatch {
(( $ctx:expr; $args:expr ) => {
$(
$( #[$attr:meta] )*
( $ctx_param:tt; $( $param:ident : $ty:ident ),* ) =>
$impl:expr
$(,)*
)*
} $(,)*) => ({
#[allow(unused_mut)]
let mut dispatcher = $crate::eval::dispatch::Dispatcher::new($ctx);
$(
$( #[$attr] )* {
dispatcher_with!((dispatcher => $ctx_param; $( $param:$ty ),*) => $impl);
}
)*
dispatcher.dispatch($args)
});
}
macro_rules! dispatch_ref {
(( $ctx:expr; $args:expr ) => {
$(
$( #[$attr:meta] )*
( $ctx_param:tt; $( $param:ident : $ty:ident ),* ) =>
$impl:expr
$(,)*
)*
} $(,)*) => ({
#[allow(unused_mut)]
let mut dispatcher = $crate::eval::dispatch::Dispatcher::new($ctx);
$(
$( #[$attr] )* {
dispatcher_with_ref!((dispatcher => $ctx_param; $( $param:$ty ),*) => $impl);
}
)*
dispatcher.dispatch($args)
});
}