input: add InputMap; dsl/output/tui: Atom->Dsl
Some checks are pending
/ build (push) Waiting to run

This commit is contained in:
🪞👃🪞 2025-04-28 04:49:00 +03:00
parent 47b7f7e7f9
commit 35ad371205
15 changed files with 374 additions and 277 deletions

View file

@ -1,48 +1,56 @@
use crate::*; use crate::*;
pub trait TryFromAtom<'a, T>: Sized {
fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option<Self> { None } pub trait TryFromDsl<'a, T>: Sized {
fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option<Self> {
None
}
fn try_from_atom (state: &'a T, value: Value<'a>) -> Option<Self> { fn try_from_atom (state: &'a T, value: Value<'a>) -> Option<Self> {
if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) } if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) }
None None
} }
} }
pub trait TryIntoAtom<T>: Sized {
pub trait TryIntoDsl<T>: Sized {
fn try_into_atom (&self) -> Option<Value>; fn try_into_atom (&self) -> Option<Value>;
} }
/// Map EDN tokens to parameters of a given type for a given context /// Map EDN tokens to parameters of a given type for a given context
pub trait Context<U>: Sized { pub trait Context<U>: Sized {
fn get (&self, _atom: &Value) -> Option<U> { fn get (&self, _atom: &Value) -> Option<U> {
None None
} }
fn get_or_fail (&self, atom: &Value) -> U { fn get_or_fail (&self, dsl: &Value) -> U {
self.get(atom).expect("no value") self.get(dsl).expect("no value")
} }
} }
impl<T: Context<U>, U> Context<U> for &T { impl<T: Context<U>, U> Context<U> for &T {
fn get (&self, atom: &Value) -> Option<U> { fn get (&self, dsl: &Value) -> Option<U> {
(*self).get(atom) (*self).get(dsl)
} }
fn get_or_fail (&self, atom: &Value) -> U { fn get_or_fail (&self, dsl: &Value) -> U {
(*self).get_or_fail(atom) (*self).get_or_fail(dsl)
} }
} }
impl<T: Context<U>, U> Context<U> for Option<T> { impl<T: Context<U>, U> Context<U> for Option<T> {
fn get (&self, atom: &Value) -> Option<U> { fn get (&self, dsl: &Value) -> Option<U> {
self.as_ref().map(|s|s.get(atom)).flatten() self.as_ref().map(|s|s.get(dsl)).flatten()
} }
fn get_or_fail (&self, atom: &Value) -> U { fn get_or_fail (&self, dsl: &Value) -> U {
self.as_ref().map(|s|s.get_or_fail(atom)).expect("no provider") self.as_ref().map(|s|s.get_or_fail(dsl)).expect("no provider")
} }
} }
/// Implement `Context` for a context and type. /// Implement `Context` for a context and type.
#[macro_export] macro_rules! provide { #[macro_export] macro_rules! provide {
// Provide a value to the EDN template // Provide a value to the EDN template
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State { impl Context<$type> for $State {
#[allow(unreachable_code)] #[allow(unreachable_code)]
fn get (&$self, atom: &Value) -> Option<$type> { fn get (&$self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* _ => return None }) Some(match dsl { $(Sym($pat) => $expr,)* _ => return None })
} }
} }
}; };
@ -50,13 +58,14 @@ impl<T: Context<U>, U> Context<U> for Option<T> {
($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$lt> Context<$lt, $type> for $State { impl<$lt> Context<$lt, $type> for $State {
#[allow(unreachable_code)] #[allow(unreachable_code)]
fn get (&$lt $self, atom: &Value) -> Option<$type> { fn get (&$lt $self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* _ => return None }) Some(match dsl { $(Sym($pat) => $expr,)* _ => return None })
} }
} }
}; };
} }
/// Implement `Context` for a context and numeric type. /// Implement `Context` for a context and numeric type.
/// ///
/// This enables support for numeric literals. /// This enables support for numeric literals.
@ -64,22 +73,23 @@ impl<T: Context<U>, U> Context<U> for Option<T> {
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation. // Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$T: $Trait> Context<$type> for $T { impl<$T: $Trait> Context<$type> for $T {
fn get (&$self, atom: &Value) -> Option<$type> { fn get (&$self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
} }
} }
}; };
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State { impl Context<$type> for $State {
fn get (&$self, atom: &Value) -> Option<$type> { fn get (&$self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None })
} }
} }
}; };
} }
/// Implement `Context` for a context and the boolean type. /// Implement `Context` for a context and the boolean type.
/// ///
/// This enables support for boolean literals. /// This enables support for boolean literals.
@ -87,14 +97,14 @@ impl<T: Context<U>, U> Context<U> for Option<T> {
// Provide a value that may also be a numeric literal in the EDN, to a generic implementation. // Provide a value that may also be a numeric literal in the EDN, to a generic implementation.
($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl<$T: $Trait> Context<$type> for $T { impl<$T: $Trait> Context<$type> for $T {
fn get (&$self, atom: &Value) -> Option<$type> { fn get (&$self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { Some(match dsl {
Num(n) => match *n { 0 => false, _ => true }, Num(n) => match *n { 0 => false, _ => true },
Sym(":false") | Sym(":f") => false, Sym(":false") | Sym(":f") => false,
Sym(":true") | Sym(":t") => true, Sym(":true") | Sym(":t") => true,
$(Sym($pat) => $expr,)* $(Sym($pat) => $expr,)*
_ => return Context::get(self, atom) _ => return Context::get(self, dsl)
}) })
} }
} }
@ -102,9 +112,9 @@ impl<T: Context<U>, U> Context<U> for Option<T> {
// Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation.
($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => {
impl Context<$type> for $State { impl Context<$type> for $State {
fn get (&$self, atom: &Value) -> Option<$type> { fn get (&$self, dsl: &Value) -> Option<$type> {
use Value::*; use Value::*;
Some(match atom { Some(match dsl {
Num(n) => match *n { 0 => false, _ => true }, Num(n) => match *n { 0 => false, _ => true },
Sym(":false") | Sym(":f") => false, Sym(":false") | Sym(":f") => false,
Sym(":true") | Sym(":t") => true, Sym(":true") | Sym(":t") => true,
@ -115,6 +125,7 @@ impl<T: Context<U>, U> Context<U> for Option<T> {
} }
}; };
} }
#[cfg(test)] #[test] fn test_edn_context () { #[cfg(test)] #[test] fn test_edn_context () {
struct Test; struct Test;
provide_bool!(bool: |self: Test|{ provide_bool!(bool: |self: Test|{

View file

@ -32,7 +32,7 @@ mod dsl_macros;
////include_str!("../../tui/examples/edn01.edn"), ////include_str!("../../tui/examples/edn01.edn"),
////include_str!("../../tui/examples/edn02.edn"), ////include_str!("../../tui/examples/edn02.edn"),
////] { ////] {
//////let items = Atom::read_all(example)?; //////let items = Dsl::read_all(example)?;
//////panic!("{layout:?}"); //////panic!("{layout:?}");
//////let content = <dyn ViewContext<::tengri_engine::tui::Tui>>::from(&layout); //////let content = <dyn ViewContext<::tengri_engine::tui::Tui>>::from(&layout);
////} ////}

View file

@ -1,54 +0,0 @@
use crate::*;
#[macro_export] macro_rules! defcom {
([$self:ident, $app:ident:$App:ty] $(($Command:ident $((
$Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr
))*))*) => {
$(#[derive(Clone, Debug)] pub enum $Command {
$($Variant $(($($Param),+))?),*
})*
$(command!(|$self: $Command, $app: $App|match $self {
$($Command::$Variant $(($($param),+))? => $expr),*
});)*
};
(|$self:ident, $app:ident:$App:ty| $($Command:ident { $(
$Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr
)* $(,)? })*) => {
$(#[derive(Clone, Debug)] pub enum $Command {
$($Variant $(($($Param),+))?),*
})*
$(command!(|$self: $Command, $app: $App|match $self {
$($Command::$Variant $(($($param),+))? => $expr),*
});)*
};
}
#[macro_export] macro_rules! command {
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
impl$(<$($l),+>)? Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}

View file

@ -0,0 +1,21 @@
use crate::*;
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
where Self: Sized
{
Ok(self.execute(state)?.map(wrap))
}
}
impl<S, T: Command<S>> Command<S> for Option<T> {
fn execute (self, _: &mut S) -> Perhaps<Self> {
Ok(None)
}
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
where Self: Sized
{
Ok(None)
}
}

162
input/src/input_dsl.rs Normal file
View file

@ -0,0 +1,162 @@
use crate::*;
/// A [Command] that can be constructed from a [Token].
pub trait DslCommand<'a, C>: TryFromDsl<'a, C> + Command<C> {}
impl<'a, C, T: TryFromDsl<'a, C> + Command<C>> DslCommand<'a, C> for T {}
/// [Input] state that can be matched against a [Value].
pub trait DslInput: Input {
fn matches_dsl (&self, token: &str) -> bool;
}
/// A pre-configured mapping of input events to commands.
pub trait KeyMap<'a, S, C: DslCommand<'a, S>, I: DslInput> {
/// Try to find a command that matches the current input event.
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C>;
}
/// A [SourceIter] can be a [KeyMap].
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for SourceIter<'a> {
fn command (&'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_dsl(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 [TokenIter] can be a [KeyMap].
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter<'a> {
fn command (&'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_dsl(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 collection of pre-configured mappings of input events to commands,
/// which may be made available subject to given conditions.
pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput> {
layers: Vec<(
Box<dyn Fn(&S)->bool + Send + Sync + 'a>,
Box<dyn KeyMap<'a, S, C, I> + Send + Sync + 'a>
)>,
}
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> Default for InputMap<'a, S, C, I> {
fn default () -> Self {
Self {
layers: vec![]
}
}
}
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> InputMap<'a, S, C, I> {
pub fn new (keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a) -> Self {
Self::default().layer(keymap)
}
pub fn layer (
mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
) -> Self {
self.add_layer(keymap);
self
}
pub fn add_layer (
&mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
) -> &mut Self {
self.add_layer_if(|_|true, keymap);
self
}
pub fn layer_if (
mut self,
condition: impl Fn(&S)->bool + Send + Sync + 'a,
keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
) -> Self {
self.add_layer_if(condition, keymap);
self
}
pub fn add_layer_if (
&mut self,
condition: impl Fn(&S)->bool + Send + Sync + 'a,
keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
) -> &mut Self {
self.layers.push((Box::new(condition), Box::new(keymap)));
self
}
}
/// An [InputMap] can be a [KeyMap].
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I> {
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C> {
todo!()
}
}
//fn handle <C> (&self, state: &mut T, input: &U) -> Perhaps<bool> {
//for layer in self.layers.iter() {
//if !(layer.0)(state) {
//continue
//}
//let command: Option<C> = SourceIter(layer.1).command::<_, _, _>(state, input);
//if let Some(command) = command {
//if let Some(undo) = command.execute(state)? {
////app.history.push(undo);
//}
//return Ok(Some(true))
//}
//}
//Ok(None)
//}
//}
//fn layer (mut self, keymap: &'static str) -> Self {
//self.add_layer(keymap);
//self
//}
//fn layer_if (mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> Self {
//self.add_layer_if(condition, keymap);
//self
//}
//fn add_layer (&mut self, keymap: &'static str) -> &mut Self {
//self.layers.push((Box::new(|_|true), keymap));
//self
//}
//fn add_layer_if (&mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> &mut Self {
//self.layers.push((Box::new(condition), keymap));
//self
//}
//}

131
input/src/input_macros.rs Normal file
View file

@ -0,0 +1,131 @@
use crate::*;
/** Implement `Command` for given `State` and collection
* of `Variant` to `handler` mappings. */
#[macro_export] macro_rules! defcom {
([$self:ident, $state:ident:$State:ty] $(($Command:ident $((
$Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr
))*))*) => {
$(#[derive(Clone, Debug)] pub enum $Command {
$($Variant $(($($Param),+))?),*
})*
$(command!(|$self: $Command, $state: $State|match $self {
$($Command::$Variant $(($($param),+))? => $expr),*
});)*
};
(|$self:ident, $state:ident:$State:ty| $($Command:ident { $(
$Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr
)* $(,)? })*) => {
$(#[derive(Clone, Debug)] pub enum $Command {
$($Variant $(($($Param),+))?),*
})*
$(command!(|$self: $Command, $state: $State|match $self {
$($Command::$Variant $(($($param),+))? => $expr),*
});)*
};
}
/** Implement `Command` for given `State` and `handler` */
#[macro_export] macro_rules! command {
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
impl$(<$($l),+>)? Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
};
}
/** Implement `DslCommand` 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 Dsl
$(
// type:name separator
:
// argument type
$type:ty
)?
),*
// rest of parameters
$(, ..$rest:ident)?
]
// bound command:
$command:expr
))* }) => {
impl<'a, $State: $Trait> TryFromDsl<'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 Dsl
$(
// type:name separator
:
// argument type
$type:ty
)?
),*
// rest of parameters
$(, ..$rest:ident)?
]
// bound command:
$command:expr
))* }) => {
impl<'a> TryFromDsl<'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);
};
}

View file

@ -1,177 +0,0 @@
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(())
}

View file

@ -1,19 +1,15 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
mod command; pub use self::command::*; pub(crate) use std::error::Error;
mod handle; pub use self::handle::*; pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
mod keymap; pub use self::keymap::*; #[cfg(test)] pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
#[cfg(feature = "dsl")] mod input_dsl;
#[cfg(feature = "dsl")] pub use self::input_dsl::*;
/// Standard error trait. mod input_macros;
pub(crate) use std::error::Error; mod input_command; pub use self::input_command::*;
mod input_handle; pub use self::input_handle::*;
/// Standard optional result type.
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
/// Standard result type.
#[cfg(test)]
pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
#[cfg(test)] #[cfg(test)]
#[test] fn test_stub_input () -> Usually<()> { #[test] fn test_stub_input () -> Usually<()> {
@ -36,3 +32,9 @@ pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
assert!(!TestInput(false).is_done()); assert!(!TestInput(false).is_done());
Ok(()) Ok(())
} }
#[cfg(all(test, feature = "dsl"))]
#[test] fn test_dsl_keymap () -> Usually<()> {
let keymap = SourceIter::new("");
Ok(())
}

View file

@ -31,7 +31,7 @@ macro_rules! transform_xy {
#[inline] pub const fn xy (item: T) -> Self { Self::XY(item) } #[inline] pub const fn xy (item: T) -> Self { Self::XY(item) }
} }
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T>
for $Enum<RenderBox<'a, E>> { for $Enum<RenderBox<'a, E>> {
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {
let mut iter = iter.clone(); let mut iter = iter.clone();
@ -78,7 +78,7 @@ macro_rules! transform_xy_unit {
#[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } #[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) }
} }
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T>
for $Enum<E::Unit, RenderBox<'a, E>> { for $Enum<E::Unit, RenderBox<'a, E>> {
fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option<Self> {
let mut iter = iter.clone(); let mut iter = iter.clone();

View file

@ -1,5 +1,6 @@
use crate::*; use crate::*;
use std::ops::Deref; use std::ops::Deref;
/// Render target. /// Render target.
pub trait Output: Send + Sync + Sized { pub trait Output: Send + Sync + Sized {
/// Unit of length /// Unit of length

View file

@ -79,8 +79,8 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
#[macro_export] macro_rules! try_delegate { #[macro_export] macro_rules! try_delegate {
($s:ident, $atom:expr, $T:ty) => { ($s:ident, $dsl:expr, $T:ty) => {
if let Some(value) = <$T>::try_from_atom($s, $atom) { if let Some(value) = <$T>::try_from_atom($s, $dsl) {
return Some(value.boxed()) return Some(value.boxed())
} }
} }
@ -89,7 +89,7 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync
#[cfg(feature = "dsl")] #[cfg(feature = "dsl")]
#[macro_export] macro_rules! try_from_expr { #[macro_export] macro_rules! try_from_expr {
(<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => { (<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => {
impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct { impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromDsl<$l, T> for $Struct {
fn try_from_expr ($state: &$l T, $iter: TokenIter<'a>) -> Option<Self> { fn try_from_expr ($state: &$l T, $iter: TokenIter<'a>) -> Option<Self> {
let mut $iter = $iter.clone(); let mut $iter = $iter.clone();
$body; $body;

View file

@ -49,8 +49,8 @@ impl TuiIn {
} }
} }
impl AtomInput for TuiIn { impl DslInput for TuiIn {
fn matches_atom (&self, token: &str) -> bool { fn matches_dsl (&self, token: &str) -> bool {
if let Some(event) = KeyMatcher::new(token).build() { if let Some(event) = KeyMatcher::new(token).build() {
&event == self.event() &event == self.event()
} else { } else {