use itertools::{
Either::{Left, Right},
Itertools,
};
use crate::{
Map, NameRef,
syntax::ast::{Arg, Args, Value},
};
#[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 >
),* $(,)? }
) => {
$( #[$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();
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");
let mut __named = __named;
$(
let $named = cmd_args!(@named __ctx, &mut __named, $named: $named_ty);
)*
$(
// 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);
)*
assert!(__named.is_empty(), "TODO: handle unknown named args");
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")
})
};
(@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
}
};
(@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)
}
#[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,
}
}
}