use crate::*; /// [Input] state that can be matched against a [Value]. pub trait AtomInput: Input { fn matches_atom (&self, token: &str) -> bool; } #[cfg(feature = "dsl")] pub trait KeyMap<'a> { /// Try to find a command that matches the current input event. fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) -> Option; } #[cfg(feature = "dsl")] impl<'a> KeyMap<'a> for SourceIter<'a> { fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) -> Option { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { iter = rest; match token { Token { value: Value::Exp(0, exp_iter), .. } => { let mut exp_iter = exp_iter.clone(); match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_atom(binding) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } }, _ => panic!("invalid config (expected symbol)") } }, _ => panic!("invalid config (expected expression)") } } None } } #[cfg(feature = "dsl")] impl<'a> KeyMap<'a> for TokenIter<'a> { fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) -> Option { let mut iter = self.clone(); while let Some(next) = iter.next() { match next { Token { value: Value::Exp(0, exp_iter), .. } => { let mut exp_iter = exp_iter.clone(); match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_atom(binding) { if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { return Some(command) } } }, _ => panic!("invalid config (expected symbol)") } }, _ => panic!("invalid config (expected expression)") } } None } } /// A [Command] that can be constructed from a [Token]. #[cfg(feature = "dsl")] pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command {} #[cfg(feature = "dsl")] impl<'a, C, T: TryFromAtom<'a, C> + Command> AtomCommand<'a, C> for T {} /** Implement `AtomCommand` for given `State` and `Command` */ #[cfg(feature = "dsl")] #[macro_export] macro_rules! atom_command { ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( // identifier $key:literal [ // named parameters $( // argument name $arg:ident // if type is not provided defaults to Atom $( // type:name separator : // argument type $type:ty )? ),* // rest of parameters $(, ..$rest:ident)? ] // bound command: $command:expr ))* }) => { impl<'a, $State: $Trait> TryFromAtom<'a, $State> for $Command { fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { let iter = iter.clone(); match iter.next() { $(Some(Token { value: Value::Key($key), .. }) => { let iter = iter.clone(); $( let next = iter.next(); if next.is_none() { panic!("no argument: {}", stringify!($arg)); } let $arg = next.unwrap(); $(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)? )* $(let $rest = iter.clone();)? return $command },)* _ => None } None } } }; ($Command:ty : |$state:ident:$State:ty| { $(( // identifier $key:literal [ // named parameters $( // argument name $arg:ident // if type is not provided defaults to Atom $( // type:name separator : // argument type $type:ty )? ),* // rest of parameters $(, ..$rest:ident)? ] // bound command: $command:expr ))* }) => { impl<'a> TryFromAtom<'a, $State> for $Command { fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { let mut iter = iter.clone(); match iter.next() { $(Some(Token { value: Value::Key($key), .. }) => { let mut iter = iter.clone(); $( let next = iter.next(); if next.is_none() { panic!("no argument: {}", stringify!($arg)); } let $arg = next.unwrap(); $(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)? )* $(let $rest = iter.clone();)? return $command }),* _ => None } } } }; (@bind $state:ident =>$arg:ident ? : $type:ty) => { let $arg: Option<$type> = Context::<$type>::get($state, $arg); }; (@bind $state:ident => $arg:ident : $type:ty) => { let $arg: $type = Context::<$type>::get_or_fail($state, $arg); }; } #[cfg(all(test, feature = "dsl"))] #[test] fn test_atom_keymap () -> Usually<()> { let keymap = SourceIter::new(""); Ok(()) }