nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
//! Commands; how to construct and how to run them.
//!
//! # Structure
//!
//! Fundamentally, every command implements [`Command`].
//! This covers the stages of:
//!
//! 1. *Construction* from the [AST] and read-only access to [`State`], may error out.
//!    This is handled by the [`Resolve`] trait, best implemented using [`parse`].
//! 2. *Running* with read-write access to [`State`].
//!
//! The advantage here is rather non-obvious:
//! *Only* construction can produce errors.[^cmd_args]
//! Running an existent command will *always* work!
//! In effect, if a command happens to be errornuous,
//! state is not affected.
//!
//! [^cmd_args]: When using [`cmd_args`] on a command type itself,
//!     you never even handle errors yourself,
//!     the macro performs all the parsing and error path work!
//!
//! [AST]: crate::syntax::ast

pub mod balance;
pub mod create;
pub mod deliver;
pub mod parse;
pub mod pay;
pub mod prelude;

use std::fmt;

pub use balance::Balance;
pub use create::{CreateConcept, CreateEntity, CreateObject};
pub use deliver::Deliver;
pub use pay::Pay;

use prelude::*;

pub type Error = error::Construction;
pub type Output = Option<Box<dyn fmt::Display>>;

/// See the module documentation.
///
/// Just a combination for requiring [`Run`] for itself and
/// [`Resolve`] (well, [`Emerge`] but that doesn't matter much) for the arguments.
/// This is split in order to allow boxing of [`Run`]
/// while still having something to refer to and implement as "command".
pub trait Command: Run + Emerge<Self::Args, Error> + Sized {
    /// What the command can be constructed from.
    ///
    /// Note that you can also use the [`cmd_args`] macro *on the command itself*,
    /// in which case you can just use `Self` here.
    /// Otherwise you'll need to write a [`Resolve`] implementation
    /// from the args type you chose to the command type.
    ///
    /// Think of this as an "entry point" from where to start making your command.
    type Args: for<'code> Emerge<ast::Stmt<'code>, Error>;

    fn boxed(self) -> Box<dyn Run>
    where
        Self: 'static,
    {
        Box::new(self)
    }
}

// somewhat hacky. would need specialization for a "proper" fix i think.
// but it works.
// and allows a command using itself as target for its arguments.
impl<T: Run, E> Resolve<T, E> for T {
    fn resolve(self, _: &State) -> Result<Self, E> {
        Ok(self)
    }
}

/// See [`Command`].
pub trait Run {
    fn run(self: Box<Self>, ctx: &mut State) -> Output;
}

pub fn construct(src: ast::Stmt<'_>, ctx: &State) -> Result<Box<dyn Run>, error::Construction> {
    use ast::Command as C;

    macro_rules! forward {
        ($cmd:ty, $src:expr, $ctx:expr) => {{
            let args = <$cmd as Command>::Args::emerge($src, $ctx)?;
            <$cmd>::emerge(args, $ctx).map(|c| Box::new(c) as Box<dyn Run>)
        }};
    }
    match src.cmd {
        C::Create(Actor::Entity) => forward!(CreateEntity, src, ctx),
        C::Create(Actor::Concept) => forward!(CreateConcept, src, ctx),
        C::Create(Actor::Object) => forward!(CreateObject, src, ctx),
        C::Pay => forward!(Pay, src, ctx),
        C::Balance => forward!(Balance, src, ctx),
        C::Deliver => forward!(Deliver, src, ctx),
    }
}