expy 0.0.2

Embeddable & extensible expression evaluator
Documentation
//! Dispatching to concrete implementations of operations.

use std::collections::HashMap;

use crate::model::{Value, Type};

use super::context::Context;
use super::error::Error;


/// Type-based dispatcher to a predefined set of implementations.
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() }
    }

    /// Register an implementation with this [`Dispatcher`].
    ///
    /// This is the mutable receiver variant of [`with`].
    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;

        // This weird dance enables us to see if the type signature has already been used
        // while doing both the key construction & map lookup only once each.
        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
    }

    /// Register an implementation with this [`Dispatcher`].
    ///
    /// This is the owned receiver variant of [`add`].
    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
    }

    /// Dispatch to one of the registered implementations using the [`Type`]s of passed [`Value`]s,
    /// or return a type error if nothing matched.
    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,
            }),
        }
    }
}


/// Convenient macro for calling `Dispatcher::with` w/o having to repeat the argument types,
/// or unpack the [`Value`]s manually.
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
        })
    };
}

/// Same as `dispatcher_with!` but borrows all arguments rather than expecting them to be Copy.
/// Useful for operating on non-Copy [`Value`]s, such as callables or symbols.
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 for dispatching to overloaded implementations of operators or functions.
/// Uses `dispatch_with!` internally, so it expects all arguments to be of Copy types.
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)
    });
}

/// Same as `dispatch!`, but borrows all arguments (i.e. uses `dispatcher_with_ref!` internally).
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)
    });
}