nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
//! Get bare [`ast::Stmt`] into meaningful command-specific arguments.

use itertools::{
    Either::{Left, Right},
    Itertools,
};

use crate::{
    Map, NameRef,
    syntax::ast::{Arg, Args, Value},
};

// yeah, this design requires a strict order of pos -> named -> optional
// otoh this is maybe not even that bad: it also enforces clarity regarding order
//
// the types are somewhat faked. they're actually just the variants of `Value` for now
// (except for actors, which are special cased out)
/// Define the arguments expected by a command.
/// The wrapped struct has an associated function `parse` generated for it,
/// returning `Result<Self, crate::error::Construction>`.
///
/// Note that actors specified as types (recall: an acor is an entity, concept or object)
/// have to exist already in order for the arguments to parse successfully.
#[macro_export]
macro_rules! cmd_args {
    (
        $( #[$struct_meta:meta] )*
        $struct_vis:vis struct $struct:ident
        { $(
            #[pos]
            $( #[$pos_meta:meta] )*
            $pos_vis:vis $pos:ident : $pos_ty:ident,
        )* $(
            #[named]
            $( #[$named_meta:meta] )*
            $named_vis:vis $named:ident : $named_ty:ident,
        )* $(
            #[named(opt)]
            $( #[$opt_meta:meta] )*
            $opt_vis:vis $opt:ident : Option< $opt_ty:ident >
        ),* $(,)? }
    ) => {
        // rustfmt can't reach me here!
        // in my evil decl macro, i can let my evil plans of putting braces on their own line
        // come to their greatest blossom! [evil laughter]
        $( #[$struct_meta] )*
        #[derive($crate::aux::Owned!)]
        $struct_vis struct $struct
        {
            $( $pos_vis $pos: $pos_ty, )*
            $( $named_vis $named: $named_ty, )*
            $( $opt_vis $opt: Option<$opt_ty>, )*
        }

        impl $crate::runtime::Resolve<$struct, $crate::runtime::cmd::Error>
            for $crate::syntax::ast::Stmt<'_>
        {
            fn resolve(
                self,
                __ctx: &$crate::runtime::State,
            ) -> ::core::result::Result<$struct, $crate::runtime::cmd::Error> {
                let (__pos, __named) = self.args.transpose();

                // go through the positional arguments and deconstruct their types one-by-one
                let mut __pos = __pos.into_iter();
                $(
                    let $pos = cmd_args!(@pos __ctx, &mut __pos, _: $pos_ty);
                )*
                assert_eq!(__pos.next(), None, "TODO: handle too many pos args");

                // same for the mandatory named arguments
                let mut __named = __named;
                $(
                    let $named = cmd_args!(@named __ctx, &mut __named, $named: $named_ty);
                )*

                // run a second pass for optional named args
                $(
                    // somewhat of a manual .map so we can error out more easily later
                    let $opt = cmd_args!(@named(opt) __ctx, &mut __named, $opt: $opt_ty);
                )*

                // otherwise we have no idea what to do with the args /shrug
                assert!(__named.is_empty(), "TODO: handle unknown named args");

                // put them all together (they're all variables in scope already)
                Ok($struct {
                    $( $pos: $pos.into(), )*
                    $( $named: $named.into(), )*
                    $( $opt: $opt.into(), )*
                })
            }
        }
    };
    (@pos $ctx:expr, $tap:expr, _ : $ty:ident) => {
        cmd_args!(@extract $ctx, $ty =>
            $tap.next().expect("TODO: handle too few pos args");
        {
            panic!("TODO: handle mismatched pos arg type")
        })
    };
    // special case: pull args `from` and `to` instead, then resolve
    // this allows most commands to have their args inline
    // NOTE: in this case, the ident is only used in the struct, not for the args!
    // the arg names are hardcoded!
    (@named $ctx:expr, $bucket:expr, $ident:ident : Dir) => {{
        let from = cmd_args!(@named $ctx, $bucket, from : Entity);
        let to = cmd_args!(@named $ctx, $bucket, to : Entity);

        Dir::new(from, to)
            .map_err($crate::runtime::cmd::Error::Same)?
    }};
    (@named $ctx:expr, $bucket:expr, $ident:ident : $ty:ident) => {
        cmd_args!(@extract $ctx, $ty =>
            $bucket
                .remove(stringify!($ident))
                .expect("TODO: handle missing mandatory named arg");
        {
            panic!("TODO: handle mismatched mandatory named arg type")
        })
    };
    (@named(opt) $ctx:expr, $bucket:expr, $ident:ident : $ty:ident) => {
        if let Some(value) = $bucket.remove(stringify!($ident)) {
            Some(
                cmd_args!(@extract $ctx, $ty => value; {
                    panic!("TODO: handle mismatched optional named arg type")
                })
            )
        } else {
            None
        }
    };
    // helper to extract actors if needed (need to check ctx here)
    // TODO: make this more elegant. somehow.
    (@extract $ctx:expr, Entity => $op:expr; $else:tt) => { {
        #[allow(unused)]
        use $crate::{syntax::ast::Value as __V, runtime::Resolve};

        let __V::Name(name) = $op else {
            $else
        };
        name.resolve($ctx)
            .map_err($crate::error::UnknownActor::Entity)?
            .clone()
    } };
    (@extract $ctx:expr, Concept => $op:expr; $else:tt) => { {
        #[allow(unused)]
        use $crate::{syntax::ast::Value as __V, runtime::Resolve};

        match $op {
            __V::Name(name) => name
                .resolve($ctx)
                .map_err($crate::error::UnknownActor::Concept),
            __V::Gtin(gtin) => gtin
                .resolve($ctx)
                .map_err($crate::error::UnknownActor::ConceptGtin),
            _ => $else,
        }
        ?
        .clone()
    } };
    (@extract $ctx:expr, Object => $op:expr; $else:tt) => { {
        #[allow(unused)]
        use $crate::{syntax::ast::Value as __V, runtime::Resolve};

        let __V::Name(name) = $op else {
            $else
        };
        name.resolve($ctx)
            .map_err($crate::error::UnknownActor::Object)?
            .clone()
    } };
    (@extract $ctx:expr, Split => $op:expr; $else:tt) => { {
        #[allow(unused)]
        use $crate::{syntax::ast::Value as __V, runtime::Resolve};

        let __V::Split(split) = $op else {
            $else
        };
        split.resolve($ctx)?
    } };
    (@extract $ctx:expr, $target:ident => $op:expr; $else:tt) => { {
        use $crate::syntax::ast::Value as __V;
        let __V::$target ( ret ) = $op else {
            $else
        };

        ret
    } };
}

impl<'tok> Args<'tok> {
    pub fn named<'this>(&'this self) -> impl Iterator<Item = (NameRef<'tok>, &'this Value<'tok>)> {
        self.0.iter().filter_map(Arg::as_named)
    }

    pub fn pos<'this>(&'this self) -> impl Iterator<Item = &'this Value<'tok>> {
        self.0.iter().filter_map(Arg::as_pos)
    }

    /// Costly columnizes the arguments.
    #[must_use]
    pub fn transpose(self) -> (Vec<Value<'tok>>, Map<NameRef<'tok>, Value<'tok>>) {
        self.0.into_iter().partition_map(|arg| match arg {
            Arg::Pos(value) => Left(value),
            Arg::Named { key, value } => Right((key, value)),
        })
    }
}

impl<'tok> Arg<'tok> {
    #[must_use]
    pub fn as_named(&self) -> Option<(NameRef<'tok>, &Value<'tok>)> {
        match self {
            Self::Named { key, value } => Some((key, value)),
            _ => None,
        }
    }

    #[must_use]
    pub fn as_pos(&self) -> Option<&Value<'tok>> {
        match self {
            Self::Pos(value) => Some(value),
            _ => None,
        }
    }
}

impl Value<'_> {
    #[must_use]
    pub fn as_name(&self) -> Option<NameRef<'_>> {
        match self {
            Self::Name(name) => Some(name),
            _ => None,
        }
    }
}