nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
//! Query the state into semantically useful types.

use std::convert::Infallible;

use crate::{
    NameRef, error,
    ext::{business::Balance, shop::Gtin},
    runtime::{
        State,
        model::{Concept, Dir, Entity, Object, Split},
    },
    syntax::ast,
};

pub type Always<T> = Result<T, Infallible>;

/// Make sense from the state on what this actually "means".
///
/// This is much like [`TryFrom`], except with [`State`] being available as context.
///
/// Some resolutions are *fallible*.
/// For example, a [`Name`] on its own doesn't convey much information.
/// But it can be resolved into e.g. an [`Entity`] —
/// however, only so if the entity was created beforehand.
///
/// Other resolutions are *infallible*.
/// For example, every entity has a balance to all others.
/// Hence, a [`Dir`] can always be resolved into a [`Balance`],
/// signified by the `E` generic being [`Infallible`].
///
/// # But... performance?? No references??
///
/// I tried. The trait resolver got really nasty to deal with
/// in [`crate::runtime::cmd::parse`].
/// Some things really need to be created on-the-fly (e.g. instantiated concepts)
/// and can't just be given out references to without further work.
/// Which is. Not today. Maybe someday.
pub trait Resolve<T, E>: Sized {
    fn resolve(self, ctx: &State) -> Result<T, E>;
}

/// [`Resolve`] but in reverse. Implemented for everything that is resolved *to*.
///
/// Kinda like how [`TryInto`] is to [`TryFrom`].
pub trait Emerge<S, E>: Sized {
    fn emerge(src: S, ctx: &State) -> Result<Self, E>;
}

impl<S, T, E> Emerge<S, E> for T
where
    S: Resolve<T, E>,
{
    fn emerge(src: S, ctx: &State) -> Result<Self, E> {
        src.resolve(ctx)
    }
}

impl Resolve<Entity, error::UnknownEntity> for NameRef<'_> {
    /// Looks up an already created [`Entity`] by name.
    fn resolve(self, ctx: &State) -> Result<Entity, error::UnknownEntity> {
        ctx.entities
            .get(self)
            .cloned()
            .ok_or_else(|| error::UnknownEntity(self.to_owned()))
    }
}

impl Resolve<Concept, error::UnknownConcept> for NameRef<'_> {
    /// Looks up an already created [`Concept`] by name.
    fn resolve(self, ctx: &State) -> Result<Concept, error::UnknownConcept> {
        ctx.concepts
            .get(self)
            .cloned()
            .ok_or_else(|| error::UnknownConcept(self.to_owned()))
    }
}

impl Resolve<Concept, error::UnknownConceptGtin> for Gtin {
    /// Looks up an already created [`Concept`]
    /// that had a GTIN specified on creation
    /// by [`Gtin`].
    ///
    /// # Caveats
    ///
    /// This might have unintended consequences with shadowing!
    /// For example, take the following script:
    ///
    /// ```text
    /// create concept A price 1€ gtin 12345678
    /// create concept A price 2€
    /// create concept A price 3€
    /// ```
    ///
    /// There are now 3 concepts with the name `A`,
    /// but only the last one with price `3€` is reachable by the name `A`.
    /// The first one with the GTIN `12345678`
    /// can be reached via that GTIN.
    /// The second one, however, is inaccessible
    /// (assuming it is not a parent of an object).
    fn resolve(self, ctx: &State) -> Result<Concept, error::UnknownConceptGtin> {
        ctx.concepts_gtin
            .get(&self)
            .cloned()
            .ok_or(error::UnknownConceptGtin(self))
    }
}

impl Resolve<Object, error::UnknownConceptGtin> for Gtin {
    fn resolve(self, ctx: &State) -> Result<Object, error::UnknownConceptGtin> {
        Concept::emerge(self, ctx).map(Concept::instantiate)
    }
}

impl Resolve<Object, error::UnknownObject> for NameRef<'_> {
    /// Looks up an already created [`Object`] by name
    /// or creates a new anonymous one if there is a [`Concept`] with the same name.
    fn resolve(self, ctx: &State) -> Result<Object, error::UnknownObject> {
        // look up directly
        ctx.objects
            .get(self)
            .cloned()
            // hmm, maybe we can create an anonymous one then?
            .or_else(|| ctx.concepts.get(self).cloned().map(Concept::instantiate))
            // nope, none of that
            .ok_or_else(|| error::UnknownObject(self.to_owned()))
    }
}

// TODO: the error type isn't quite right. this would need its own error tbh
impl Resolve<Dir, error::Construction> for (NameRef<'_>, NameRef<'_>) {
    /// Look up the two names as [`Entity`], then construct a [`Dir`].
    fn resolve(self, ctx: &State) -> Result<Dir, error::Construction> {
        let (source, target) = self;
        let lookup = |side: &str| side.resolve(ctx).map_err(error::UnknownActor::Entity);

        let dir = Dir::new(lookup(source)?, lookup(target)?)?;
        Ok(dir)
    }
}

impl Resolve<Balance, Infallible> for Dir {
    /// Returns how much the [`Dir::source`] owes [`Dir::target`].
    ///
    /// If the balance is _negative_, that means the balance is _in reverse_,
    /// how much [`Dir::target`] owes [`Dir::source`] in absolute value!
    fn resolve(self, ctx: &State) -> Always<Balance> {
        // clone could certainly be avoided somehow since they have the same memory repr.
        // but this is, like, fine. /shrug
        let mut value = ctx
            .balances
            .get(&self.clone().into())
            .cloned()
            .unwrap_or_default();
        value.align_to(&self);

        Ok(Balance::new(self, value))
    }
}

impl Resolve<Split, error::BothZero> for ast::Split {
    fn resolve(self, _: &State) -> Result<Split, error::BothZero> {
        let Self { from, to } = self;
        Split::new(from, to)
    }
}