tabula rasa

This commit is contained in:
🪞👃🪞 2025-03-02 14:31:04 +02:00
commit 47b3413d7d
76 changed files with 7000 additions and 0 deletions

28
input/src/command.rs Normal file
View file

@ -0,0 +1,28 @@
use crate::*;
#[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)
}
}

73
input/src/event_map.rs Normal file
View file

@ -0,0 +1,73 @@
use crate::*;
pub struct EventMap<'a, S, I: PartialEq, C> {
pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option<C>)],
pub fallback: Option<&'a dyn Fn(&S, &I) -> Option<C>>
}
impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> {
pub fn handle (&self, state: &S, input: &I) -> Option<C> {
for (binding, handler) in self.bindings.iter() {
if input == binding {
return handler(state)
}
}
if let Some(fallback) = self.fallback {
fallback(state, input)
} else {
None
}
}
}
#[macro_export] macro_rules! keymap {
(
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
{ $($key:expr => $handler:expr),* $(,)? } $(,)?
) => {
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
fallback: None,
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
};
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
};
(
$(<$lt:lifetime>)? $KEYS:ident = |$state:ident: $State:ty, $input:ident: $Input:ty| $Command:ty
{ $($key:expr => $handler:expr),* $(,)? }, $default:expr
) => {
pub const $KEYS: EventMap<'static, $State, $Input, $Command> = EventMap {
fallback: Some(&|$state, $input|Some($default)),
bindings: &[ $(($key, &|$state|Some($handler)),)* ]
};
input_to_command!($(<$lt>)? $Command: |$state: $State, input: $Input|$KEYS.handle($state, input)?);
};
}
#[macro_export] macro_rules! input_to_command {
(<$($l:lifetime),+> $Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
impl<$($l),+> InputToCommand<$Input, $State> for $Command {
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
Some($handler)
}
}
};
($Command:ty: |$state:ident:$State:ty, $input:ident:$Input:ty| $handler:expr) => {
impl InputToCommand<$Input, $State> for $Command {
fn input_to_command ($state: &$State, $input: &$Input) -> Option<Self> {
Some($handler)
}
}
}
}
pub trait InputToCommand<I, S>: Command<S> + Sized {
fn input_to_command (state: &S, input: &I) -> Option<Self>;
fn execute_with_state (state: &mut S, input: &I) -> Perhaps<bool> {
Ok(if let Some(command) = Self::input_to_command(state, input) {
let _undo = command.execute(state)?;
Some(true)
} else {
None
})
}
}

75
input/src/input.rs Normal file
View file

@ -0,0 +1,75 @@
use crate::*;
use std::sync::{Mutex, Arc, RwLock};
/// Event source
pub trait Input: Send + Sync + Sized {
/// Type of input event
type Event;
/// Result of handling input
type Handled; // TODO: make this an Option<Box dyn Command<Self>> containing the undo
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// Implement the [Handle] trait.
#[macro_export] macro_rules! handle {
(|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl<E: Engine> Handle<E> for $Struct {
fn handle (&mut $self, $input: &E) -> Perhaps<E::Handled> {
$handler
}
}
};
($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &$E) -> Perhaps<<$E as Input>::Handled> {
$handler
}
}
}
}
/// Handle input
pub trait Handle<E: Input>: Send + Sync {
fn handle (&mut self, _input: &E) -> Perhaps<E::Handled> {
Ok(None)
}
}
impl<E: Input, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<E: Input, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self {
handle.handle(context)
} else {
Ok(None)
}
}
}
impl<H, E: Input> Handle<E> for Mutex<H> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.get_mut().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for RwLock<H> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
impl<H, E: Input> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
fn handle (&mut self, context: &E) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

227
input/src/keymap.rs Normal file
View file

@ -0,0 +1,227 @@
use crate::*;
/// [Input] state that can be matched against a [Value].
pub trait AtomInput: Input {
fn matches_atom (&self, token: &str) -> bool;
}
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>;
}
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
}
}
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].
pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command<C> {}
impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {}
/** Implement `AtomCommand` for given `State` and `Command` */
#[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);
};
}
//pub struct SourceKeyMap<'a>(&'a str);
//impl<'a> KeyMap for SourceKeyMap<'a> {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//todo!();
//None
//}
//}
//pub struct ParsedKeyMap<'a>(TokensIterator<'a>);
//impl<'a> KeyMap for ParsedKeyMap<'a> {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//todo!();
//None
//}
//}
//pub struct RefKeyMap<'a>(TokensIterator<'a>);
//impl<'a> KeyMap for RefKeyMap<'a> {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//todo!();
////for token in self.0 {
////match token?.kind() {
////TokenKind::Exp => match atoms.as_slice() {
////[key, command, args @ ..] => match (key.kind(), key.text()) {
////(TokenKind::Sym, key) => {
////if input.matches_atom(key) {
////let command = C::from_atom(state, command, args);
////if command.is_some() {
////return command
////}
////}
////},
////_ => panic!("invalid config: {item}")
////},
////_ => panic!("invalid config: {item}")
////}
////_ => panic!("invalid config: {item}")
////}
////}
//None
//}
//}
//pub struct ArcKeyMap(Vec<ArcAtom>);
//impl KeyMap for ArcKeyMap {
//fn command <S, C: AtomCommand<S>> (&self, state: &S, input: &AtomInput) -> Option<C> {
//for atom in self.0.iter() {
//match atom {
//ArcAtom::Exp(atoms) => match atoms.as_slice() {
//[key, command, args @ ..] => match (key.kind(), key.text()) {
//(TokenKind::Sym, key) => {
//if input.matches_atom(key) {
//let command = C::from_atom(state, command, args);
//if command.is_some() {
//return command
//}
//}
//},
//_ => panic!("invalid config: {atom}")
//},
//_ => panic!("invalid config: {atom}")
//}
//_ => panic!("invalid config: {atom}")
//}
//}
//None
//}
//}
#[cfg(test)] #[test] fn test_atom_keymap () -> Usually<()> {
let keymap = SourceIter::new("");
Ok(())
}

32
input/src/lib.rs Normal file
View file

@ -0,0 +1,32 @@
#![feature(associated_type_defaults)]
mod input; pub use self::input::*;
mod command; pub use self::command::*;
mod keymap; pub use self::keymap::*;
//mod event_map; pub use self::event_map::*;
pub(crate) use ::tek_edn::*;
/// Standard error trait.
pub(crate) use std::error::Error;
/// Standard result type.
#[cfg(test)] pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
#[cfg(test)] #[test] fn test_stub_input () -> Usually<()> {
use crate::*;
struct TestInput(bool);
enum TestEvent { Test1 }
impl Input for TestInput {
type Event = TestEvent;
type Handled = ();
fn event (&self) -> &Self::Event {
&TestEvent::Test1
}
fn is_done (&self) -> bool {
self.0
}
fn done (&self) {}
}
let _ = TestInput(true).event();
assert!(TestInput(true).is_done());
assert!(!TestInput(false).is_done());
Ok(())
}