mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 11:46:42 +01:00
177 lines
6.2 KiB
Rust
177 lines
6.2 KiB
Rust
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 <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
|
-> Option<C>;
|
|
}
|
|
|
|
#[cfg(feature = "dsl")]
|
|
impl<'a> KeyMap<'a> for SourceIter<'a> {
|
|
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
|
-> Option<C>
|
|
{
|
|
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 <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
|
-> Option<C>
|
|
{
|
|
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<C> {}
|
|
|
|
#[cfg(feature = "dsl")]
|
|
impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> 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<Self> {
|
|
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<Self> {
|
|
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(())
|
|
}
|