diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index f98130b..c22e5d6 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,71 +1,55 @@ use crate::*; -/// Implement the [Dsl] trait, which boils down to -/// specifying two types and providing an expression. -#[macro_export] macro_rules! dsl { - ($T:ty: |$self:ident:$S:ty, $iter:ident|$expr:expr) => { - impl ::tengri::dsl::Dsl<$T> for $S { - fn take <'state, 'source> ( - &'state $self, $iter: &mut ::tengri::dsl::TokenIter<'source>, - ) -> ::tengri::Perhaps<$T> { - $expr - } - } - } -} - -/// Maps a sequencer of EDN tokens to parameters of supported types -/// for a given context. -pub trait Dsl: Sized { - fn take <'state, 'source> (&'state self, _: &mut TokenIter<'source>) -> Perhaps { - unimplemented!() +/// Map EDN tokens to parameters of a given type for a given context +/// TODO: Replace both [Context] and [TryFromDsl] with this trait +/// which returns a [Result]. +pub trait Dsl: Sized { + fn take <'state, 'source> (_: &'state Self, _: &mut TokenIter<'source>) -> Perhaps { + Ok(None) } fn take_or_fail <'state, 'source> ( - &'state self, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - if let Some(value) = Dsl::::take(self, token)? { + state: &'state Self, iter: &mut TokenIter<'source> + ) -> Usually { + if let Some(value) = Self::take(state, iter)? { Ok(value) } else { - Result::Err(error.into()) + Result::Err("not found".into()) // TODO add info and error type } } } -pub trait FromDsl<'state, State>: Sized { - fn take_from <'source: 'state> (state: &'state State, _token: &mut TokenIter<'source>) - -> Perhaps - { - unimplemented!() +/// Map EDN tokens to parameters of a given type for a given context +pub trait Context<'state, U>: Sized { + fn get <'source> (&'state self, _iter: &mut TokenIter<'source>) -> Option { + None } - fn take_from_or_fail <'source: 'state> ( - state: &'state State, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - if let Some(value) = FromDsl::::take_from(state, token)? { - Ok(value) - } else { - Result::Err(error.into()) +} + +impl<'state, T: Context<'state, U>, U> Context<'state, U> for &T { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + (*self).get(iter) + } +} + +impl<'state, T: Context<'state, U>, U> Context<'state, U> for Option { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + self.as_ref().map(|s|s.get(iter)).flatten() + } +} + +pub trait TryFromDsl<'state, T>: Sized { + fn try_from_expr <'source: 'state> ( + _state: &'state T, _iter: &mut TokenIter<'source> + ) -> Option { + None + } + fn try_from_atom <'source: 'state> ( + state: &'state T, value: Value<'source> + ) -> Option { + if let Exp(0, mut iter) = value { + return Self::try_from_expr(state, &mut iter) } + None } } -//impl, U> Dsl for &T { - //fn take <'state, 'source> (&'state self, iter: &mut TokenIter<'source>) -> Perhaps { - //(*self).take(iter) - //} -//} - -//impl, U> Dsl for Option { - //fn take <'state, 'source> (&'state self, iter: &mut TokenIter<'source>) -> Perhaps { - //Ok(self.as_ref().map(|s|s.take(iter)).transpose()?.flatten()) - //} -//} - -impl<'state, X, Y> Dsl for Y where Y: FromDsl<'state, X> { -} - -//impl> Dsl for T { -//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index dfcbd72..4575e24 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,18 +1,25 @@ use crate::*; use std::marker::PhantomData; +/// A [Command] that can be constructed from a [Token]. +pub trait DslCommand<'state, C>: TryFromDsl<'state, C> + Command {} + +impl<'state, C, T: TryFromDsl<'state, C> + Command> DslCommand<'state, C> for T {} + /// [Input] state that can be matched against a [Value]. -pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } +pub trait DslInput: Input { + fn matches_dsl (&self, token: &str) -> bool; +} /// A pre-configured mapping of input events to commands. -pub trait KeyMap, C: Command, I: DslInput> { +pub trait KeyMap<'state, S, C: DslCommand<'state, S>, I: DslInput> { /// Try to find a command that matches the current input event. - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; + fn command (&'state self, state: &'state S, input: &'state I) -> Option; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceIter<'source> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { +impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> for SourceIter<'state> { + fn command (&'state self, state: &'state S, input: &'state I) -> Option { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { iter = rest; @@ -22,8 +29,8 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceI match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Dsl::::take(state, &mut exp_iter)? { - return Ok(Some(command)) + if let Some(command) = C::try_from_expr(state, &mut exp_iter) { + return Some(command) } } }, @@ -33,13 +40,13 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceI _ => panic!("invalid config (expected expression)") } } - Ok(None) + None } } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIter<'source> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { +impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> for TokenIter<'state> { + fn command (&'state self, state: &'state S, input: &'state I) -> Option { let mut iter = self.clone(); while let Some(next) = iter.next() { match next { @@ -48,8 +55,8 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIt match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Dsl::::take(state, &mut e)? { - return Ok(Some(command)) + if let Some(command) = C::try_from_expr(state, &mut e) { + return Some(command) } } }, @@ -59,29 +66,44 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIt _ => panic!("invalid config (expected expression, got: {next:?})") } } - Ok(None) + None } } -pub type InputLayerCond = BoxUsually + Send + Sync>; +pub type InputLayerCond<'state, S> = Boxbool + Send + Sync + 'state>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. -pub struct InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { - __: PhantomData<(S, C, I)>, - pub layers: Vec<(InputLayerCond, M)>, +pub struct InputMap<'state, S, C, I, M> +where + C: DslCommand<'state, S>, + I: DslInput, + M: KeyMap<'state, S, C, I> + Send + Sync +{ + __: &'state PhantomData<(S, C, I)>, + pub layers: Vec<(InputLayerCond<'state, S>, M)>, } -impl Default for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl<'state, S, C, I, M> Default for InputMap<'state, S, C, I, M> +where + C: DslCommand<'state, S>, + I: DslInput, + M: KeyMap<'state, S, C, I> + Send + Sync +{ fn default () -> Self { - Self { __: PhantomData, layers: vec![] } + Self { + __: &PhantomData, + layers: vec![] + } } } -impl InputMap -where S: Dsl + 'static, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl<'state, S, C, I, M> InputMap<'state, S, C, I, M> +where + C: DslCommand<'state, S>, + I: DslInput, + M: KeyMap<'state, S, C, I> + Send + Sync +{ pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -90,38 +112,46 @@ where S: Dsl + 'static, C: Command, I: DslInput, M: KeyMap + Send self } pub fn add_layer (&mut self, keymap: M) -> &mut Self { - self.add_layer_if(Box::new(|_|Ok(true)), keymap); + self.add_layer_if(Box::new(|_|true), keymap); self } - pub fn layer_if (mut self, condition: InputLayerCond, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputLayerCond<'state, S>, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if (&mut self, condition: InputLayerCond, keymap: M) -> &mut Self { + pub fn add_layer_if (&mut self, condition: InputLayerCond<'state, S>, keymap: M) -> &mut Self { self.layers.push((Box::new(condition), keymap)); self } } -impl std::fmt::Debug for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl<'state, S, C, I, M> std::fmt::Debug for InputMap<'state, S, C, I, M> +where + C: DslCommand<'state, S>, + I: DslInput, + M: KeyMap<'state, S, C, I> + Send + Sync +{ fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "[InputMap: {} layer(s)]", self.layers.len()) } } /// An [InputMap] can be a [KeyMap]. -impl KeyMap for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { +impl<'state, S, C, I, M> KeyMap<'state, S, C, I> for InputMap<'state, S, C, I, M> +where + C: DslCommand<'state, S>, + I: DslInput, + M: KeyMap<'state, S, C, I> + Send + Sync +{ + fn command (&'state self, state: &'state S, input: &'state I) -> Option { for (condition, keymap) in self.layers.iter() { - if !condition(state)? { + if !condition(state) { continue } - if let Some(command) = keymap.keybind_resolve(state, input)? { - return Ok(Some(command)) + if let Some(command) = keymap.command(state, input) { + return Some(command) } } - Ok(None) + None } } diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index a35659b..d4c7fb3 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,42 +36,38 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for Align> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - if let Some(Token { value: Value::Key(key), .. }) = iter.peek() { - match key { - "align/c"|"align/x"|"align/y"| - "align/n"|"align/s"|"align/e"|"align/w"| - "align/nw"|"align/sw"|"align/ne"|"align/se" => { - let _ = iter.next().unwrap(); - let content = if let Some(content) = state.get_content(&mut iter.clone())? { - content - } else { - panic!("no content corresponding to {:?}", &iter); - }; - return Ok(Some(match key { - "align/c" => Self::c(content), - "align/x" => Self::x(content), - "align/y" => Self::y(content), - "align/n" => Self::n(content), - "align/s" => Self::s(content), - "align/e" => Self::e(content), - "align/w" => Self::w(content), - "align/nw" => Self::nw(content), - "align/ne" => Self::ne(content), - "align/sw" => Self::sw(content), - "align/se" => Self::se(content), - _ => unreachable!() - })) - }, - _ => return Ok(None) - } - } else { - Ok(None) +try_from_expr!(<'source, 'state, E>: Align>: |state, iter|{ + if let Some(Token { value: Value::Key(key), .. }) = iter.peek() { + match key { + "align/c"|"align/x"|"align/y"| + "align/n"|"align/s"|"align/e"|"align/w"| + "align/nw"|"align/sw"|"align/ne"|"align/se" => { + let _ = iter.next().unwrap(); + let content = iter.next().expect("no content specified"); + let content = if let Some(content) = state.get_content(&content.value) { + content + } else { + panic!("no content corresponding to {:?}", &content); + }; + return Some(match key { + "align/c" => Self::c(content), + "align/x" => Self::x(content), + "align/y" => Self::y(content), + "align/n" => Self::n(content), + "align/s" => Self::s(content), + "align/e" => Self::e(content), + "align/w" => Self::w(content), + "align/nw" => Self::nw(content), + "align/ne" => Self::ne(content), + "align/sw" => Self::sw(content), + "align/se" => Self::se(content), + _ => unreachable!() + }) + }, + _ => return None } } -} +}); impl Align { #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 2819bbf..300d244 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,40 +21,30 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for Bsp, RenderBox<'state, E>> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - Ok(if let Some(Token { - value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), - .. - }) = iter.peek() { - let base = iter.clone(); - return Ok(Some(match iter.next() { - Some(Token { value: Value::Key("bsp/n"), .. }) => - Self::n(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - Some(Token { value: Value::Key("bsp/s"), .. }) => - Self::s(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - Some(Token { value: Value::Key("bsp/e"), .. }) => - Self::e(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - Some(Token { value: Value::Key("bsp/w"), .. }) => - Self::w(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - Some(Token { value: Value::Key("bsp/a"), .. }) => - Self::a(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - Some(Token { value: Value::Key("bsp/b"), .. }) => - Self::b(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), - _ => unreachable!(), - })) - } else { - None - }) +try_from_expr!(<'source, 'state, E>: Bsp, RenderBox<'state, E>>: |state, iter| { + if let Some(Token { value: Value::Key(key), .. }) = iter.peek() { + match key { + "bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b" => { + let original = iter.clone(); + let _ = iter.next().unwrap(); + let c1 = iter.next().unwrap_or_else(||panic!("no content1 specified: {original:?}")); + let c2 = iter.next().unwrap_or_else(||panic!("no content2 specified: {original:?}")); + let c1 = state.get_content(&c1.value).expect("no content1 provided"); + let c2 = state.get_content(&c2.value).expect("no content2 provided"); + return Some(match key { + "bsp/n" => Self::n(c1, c2), + "bsp/s" => Self::s(c1, c2), + "bsp/e" => Self::e(c1, c2), + "bsp/w" => Self::w(c1, c2), + "bsp/a" => Self::a(c1, c2), + "bsp/b" => Self::b(c1, c2), + _ => unreachable!(), + }) + }, + _ => return None + } } -} +}); impl Bsp { #[inline] pub const fn n (a: A, b: B) -> Self { Self(North, a, b) } #[inline] pub const fn s (a: A, b: B) -> Self { Self(South, a, b) } diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index cfdf2fd..578210e 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -19,41 +19,35 @@ impl Either { } #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for When> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - Ok(if let Some(Token { - value: Value::Key("when"), - .. - }) = iter.peek() { - let base = iter.clone(); - return Ok(Some(Self( - state.take(iter)?.unwrap_or_else(||panic!("cond: no condition: {base:?}")), - state.get_content_or_fail(iter)? - ))) - } else { - None - }) +try_from_expr!(<'source, 'state, E>: When>: |state, iter| { + if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { + let _ = iter.next().unwrap(); + let content = iter.next().expect("no content specified").value; + return Some(Self( + state.get(&mut iter) + .unwrap_or_else(||panic!("cond: no condition: {iter:?}")), + state.get_content(&content) + .unwrap_or_else(||panic!("cond: no content for {:?}: {iter:?}", &content)) + )) } -} +}); #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for Either, RenderBox<'state, E>> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { - let base = iter.clone(); - let _ = iter.next().unwrap(); - //panic!("{iter:?}"); - return Ok(Some(Self( - state.take(iter)?.unwrap_or_else(||panic!("either: no condition: {base:?}")), - state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?, - ))) - } - Ok(None) +try_from_expr!(<'source, 'state, E>: Either, RenderBox<'state, E>>: |state, iter| { + if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { + let base = iter.clone(); + let _ = iter.next().unwrap(); + //panic!("{iter:?}"); + return Some(Self( + state.get(&mut iter) + .unwrap_or_else(||panic!("either: no condition: {base:?}")), + state.get_content(&iter.next().expect("no content specified").value) + .unwrap_or_else(||panic!("either: no content 1: {base:?}")), + state.get_content(&iter.next().expect("no alternate specified").value) + .unwrap_or_else(||panic!("either: no content 2: {base:?}")), + )) } -} +}); impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 80d50ce..8cb93ff 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -39,22 +39,30 @@ macro_rules! transform_xy { } } #[cfg(feature = "dsl")] - impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - FromDsl<'state, T> for $Enum> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { + impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> + for $Enum> { + fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) + -> Option + { + let mut iter = iter.clone(); if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { - let mut base = iter.clone(); - return Ok(Some(match iter.next() { - Some(Token{value:Value::Key($x),..}) => - Self::x(state.get_content_or_fail(iter)?), - Some(Token{value:Value::Key($y),..}) => - Self::y(state.get_content_or_fail(iter)?), - Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.get_content_or_fail(iter)?), - _ => unreachable!() - })) + if k == $x || k == $y || k == $xy { + let _ = iter.next().unwrap(); + let token = iter.next().expect("no content specified"); + let content = if let Some(content) = state.get_content(&token.value) { + content + } else { + panic!("no content corresponding to {:?}", &token.value); + }; + return Some(match k { + $x => Self::x(content), + $y => Self::y(content), + $xy => Self::xy(content), + _ => unreachable!() + }) + } } - Ok(None) + None } } impl> Content for $Enum { @@ -84,30 +92,31 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - FromDsl<'state, T> for $Enum> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - Ok(if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = iter.peek() { - let mut base = iter.clone(); - Some(match iter.next() { - Some(Token { value: Value::Key($x), .. }) => Self::x( - state.take_or_fail(iter, "no unit specified")?, - state.get_content_or_fail(iter)?, - ), - Some(Token { value: Value::Key($y), .. }) => Self::y( - state.take_or_fail(iter, "no unit specified")?, - state.get_content_or_fail(iter)?, - ), - Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.take_or_fail(iter, "no unit specified")?, - state.take_or_fail(iter, "no unit specified")?, - state.get_content_or_fail(iter)? - ), - _ => unreachable!(), - }) - } else { - None - }) + impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> + for $Enum> { + fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option { + let mut iter = iter.clone(); + if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { + if k == $x || k == $y { + let _ = iter.next().unwrap(); + let u = state.get(&mut iter).expect("no unit specified"); + let c = state.get_content(&iter.next().expect("no content specified").value) + .expect("no content provided"); + return Some(match k { + $x => Self::x(u, c), + $y => Self::y(u, c), + _ => unreachable!(), + }) + } else if k == $xy { + let _ = iter.next().unwrap(); + let u = state.get(&mut iter).expect("no unit specified"); + let v = state.get(&mut iter).expect("no unit specified"); + let c = state.get_content(&iter.next().expect("no content specified").value) + .expect("no content provided"); + return Some(Self::xy(u, v, c)) + } + } + None } } impl> Content for $Enum { diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs index 9ed5c2c..a894b60 100644 --- a/output/src/space/measure.rs +++ b/output/src/space/measure.rs @@ -85,7 +85,7 @@ impl Measure { pub fn format (&self) -> Arc { format!("{}x{}", self.w(), self.h()).into() } - pub fn of > (&self, item: T) -> Bsp, T> { + pub fn of > (&self, item: T) -> Bsp, T> { Bsp::b(Fill::xy(self), item) } } diff --git a/output/src/view.rs b/output/src/view.rs index 8f40cc2..484194d 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -1,63 +1,114 @@ use crate::*; -#[cfg(feature = "dsl")] -#[macro_export] macro_rules! try_delegate { - ($s:ident, $dsl:expr, $T:ty) => { - let value: Option<$T> = FromDsl::take_from($s, $dsl)?; - if let Some(value) = value { - return Ok(Some(value.boxed())) +#[macro_export] macro_rules! view { + ($Output:ty: |$self:ident: $State:ty| $expr:expr; { + $($sym:literal => $body:expr),* $(,)? + }) => { + impl Content<$Output> for $State { + fn content (&$self) -> impl Render<$Output> { $expr } } + impl<'a> ViewContext<'a, $Output> for $State { + fn get_content_sym (&'a $self, value: &Value<'a>) -> Option> { + if let Value::Sym(s) = value { + match *s { + $($sym => Some($body.boxed()),)* + _ => None + } + } else { + panic!("expected content, got: {value:?}") + } + } + } + } +} + +// An ephemeral wrapper around view state and view description, +// that is meant to be constructed and returned from [Content::content]. +#[cfg(feature = "dsl")] +pub struct View<'a, T>( + pub &'a T, + pub TokenIter<'a> +); + +#[cfg(feature = "dsl")] +impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content for View<'a, T> { + fn content (&self) -> impl Render { + let mut iter = self.1.clone(); + while let Some(Token { value, .. }) = iter.next() { + if let Some(content) = self.0.get_content(&value) { + return Some(content) + } + } + return None } } // Provides components to the view. #[cfg(feature = "dsl")] pub trait ViewContext<'state, E: Output + 'state>: Send + Sync - + Dsl - + Dsl - + Dsl + + Context<'state, bool> + + Context<'state, usize> + + Context<'state, E::Unit> { - fn get_content_or_fail <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) - -> Usually> - { - let base = iter.clone(); - if let Some(content) = self.get_content(iter)? { - Ok(content) - } else { - Err(format!("not found: {iter:?}").into()) + fn get_content <'source: 'state> (&'state self, value: &Value<'source>) -> Option> { + match value { + Value::Sym(_) => self.get_content_sym(value), + Value::Exp(_, _) => self.get_content_exp(value), + _ => panic!("only :symbols and (expressions) accepted here, got: {value:?}") } } - fn get_content <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) - -> Perhaps> + fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) + -> Option>; + fn get_content_exp <'source: 'state> (&'state self, value: &Value<'source>) + -> Option> { - match iter.peek() { - Some(Token { value: Value::Sym(_), .. }) => - self.get_content_sym(iter), - Some(Token { value: Value::Exp(_, _), .. }) => - self.get_content_exp(iter), - None => Ok(None), - _ => panic!("only :symbols and (expressions) accepted here") - } - } - fn get_content_sym <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) - -> Perhaps>; - fn get_content_exp <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) - -> Perhaps> - { - try_delegate!(self, iter, When::>); - try_delegate!(self, iter, Either::, RenderBox<'state, E>>); - try_delegate!(self, iter, Align::>); - try_delegate!(self, iter, Bsp::, RenderBox<'state, E>>); - try_delegate!(self, iter, Fill::>); - try_delegate!(self, iter, Fixed::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Min::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Max::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Shrink::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Expand::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Push::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Pull::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Margin::<_, RenderBox<'state, E>>); - try_delegate!(self, iter, Padding::<_, RenderBox<'state, E>>); - Ok(None) + try_delegate!(self, *value, When::>); + try_delegate!(self, *value, Either::, RenderBox<'state, E>>); + try_delegate!(self, *value, Align::>); + try_delegate!(self, *value, Bsp::, RenderBox<'state, E>>); + try_delegate!(self, *value, Fill::>); + try_delegate!(self, *value, Fixed::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Min::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Max::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Shrink::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Expand::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Push::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Pull::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Margin::<_, RenderBox<'state, E>>); + try_delegate!(self, *value, Padding::<_, RenderBox<'state, E>>); + None + } +} + +#[cfg(feature = "dsl")] +#[macro_export] macro_rules! try_delegate { + ($s:ident, $dsl:expr, $T:ty) => { + if let Some(value) = <$T>::try_from_atom($s, $dsl) { + return Some(value.boxed()) + } + } +} + +#[cfg(feature = "dsl")] +#[macro_export] macro_rules! try_from_expr { + (< + $lt_source:lifetime, + $lt_state:lifetime, + $Output:ident + >: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => { + impl< + $lt_state, + $Output: Output + $lt_state, + T: ViewContext<$lt_state, $Output> + > TryFromDsl<$lt_state, T> for $Struct { + fn try_from_expr <$lt_source: $lt_state> ( + $state: &$lt_state T, + $iter: &mut TokenIter<$lt_source> + ) -> Option { + let mut $iter = $iter.clone(); + $body; + None + } + } } } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 27f68ff..123b11b 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -52,111 +52,49 @@ impl CommandImpl { impl ToTokens for CommandDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(CommandMeta(state), CommandImpl(block, exposed)) = self; - - let command_enum = &block.self_ty; - - let variants = exposed.values().map(|arm|{ - let mut out = TokenStream2::new(); - out.append(arm.to_enum_variant_ident()); - //let ident = &arm.0; - if arm.has_args() { - out.append(Group::new(Delimiter::Brace, { - let mut out = TokenStream2::new(); - for (arg, ty) in arm.args() { - write_quote_to(&mut out, quote! { #arg : #ty , }); - } - out - })); - } - out.append(Punct::new(',', Alone)); - out - }); - - let matchers = exposed.values().map(|arm|{ - let key = LitStr::new(&arm.to_key(), Span::call_site()); - let variant = { - let mut out = TokenStream2::new(); - out.append(arm.to_enum_variant_ident()); - let ident = &arm.0; - if arm.has_args() { - out.append(Group::new(Delimiter::Brace, { - let mut out = TokenStream2::new(); - for (arg, ty) in arm.args() { - write_quote_to(&mut out, quote! { - #arg: Dsl::take_or_fail(self, words)?, - }); - } - out - })); - } - out - }; - write_quote(quote! { - Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { - let mut words = words.clone(); - Some(#command_enum::#variant) - }, - //Some(::tengri::dsl::Token { - //value: ::tengri::dsl::Value::Key(#key), .. - //}) => { - //let mut iter = iter.clone(); Some(#command_enum::#variant) - //}, - }) - }); - - let implementations = exposed.values().map(|arm|{ - let ident = &arm.0; - let variant = { - let mut out = TokenStream2::new(); - out.append(arm.to_enum_variant_ident()); - //let ident = &arm.0; - if arm.has_args() { - out.append(Group::new(Delimiter::Brace, { - let mut out = TokenStream2::new(); - for (arg, _ty) in arm.args() { - write_quote_to(&mut out, quote! { #arg , }); - } - out - })); - } - out - }; - let give_rest = write_quote(quote! { /*TODO*/ }); - let give_args = arm.args() - .map(|(arg, _ty)|write_quote(quote! { #arg, })) - .collect::>(); - write_quote(quote! { - #command_enum::#variant => - #command_enum::#ident(state, #(#give_args)* #give_rest), - }) - }); - + let Self(CommandMeta(target), CommandImpl(block, exposed)) = self; + let enumeration = &block.self_ty; + let variants = exposed.values().map(|x|x.to_enum_variant_def()); + let matchers = exposed.values().map(CommandArm::to_matcher); + let implementations = exposed.values().map(CommandArm::to_implementation); write_quote_to(out, quote! { /// Generated by [tengri_proc]. - #[derive(Clone, Debug)] pub enum #command_enum { #(#variants)* } - /// Not generated by [tengri_proc]. + #[derive(Clone, Debug)] + pub enum #enumeration { + #(#variants)* + } #block /// Generated by [tengri_proc]. - impl ::tengri::input::Command<#state> for #command_enum { - fn execute (self, state: &mut #state) -> Perhaps { - match self { #(#implementations)* } + impl<'state> ::tengri::dsl::TryFromDsl<'state, #target> for #enumeration { + fn try_from_expr <'source: 'state> ( + state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> + ) -> Option { + let mut iter = iter.clone(); + let token = iter.next(); + match token { + #(#matchers)* + _ => None + } } } /// Generated by [tengri_proc]. - impl ::tengri::dsl::Dsl<#command_enum> for #state { - fn take <'source, 'state> ( - &'state self, words: &mut TokenIter<'source> - ) - -> ::tengri::Perhaps<#command_enum> + impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { + fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) + -> Option<#enumeration> { - let mut words = words.clone(); - let token = words.next(); - todo!()//Ok(match token { #(#matchers)* _ => None }) + use ::tengri::dsl::TryFromDsl; + #enumeration::try_from_expr(self, iter) + } + } + /// Generated by [tengri_proc]. + impl ::tengri::input::Command<#target> for #enumeration { + fn execute (self, state: &mut #target) -> Perhaps { + match self { + #(#implementations)* + } } } }); - //if exposed.len() > 0 { //panic!("{:#?}", block.self_ty); //if let Type::Path(ref path) = *block.self_ty { @@ -208,4 +146,61 @@ impl CommandArm { out.append(Punct::new(',', Alone)); out } + fn to_enum_variant_bind (&self) -> TokenStream2 { + let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); + let ident = &self.0; + if self.has_args() { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for (arg, ty) in self.args() { + //let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + //quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); + let give_err = format!("{}: missing value for \"{}\" ({}): {{:#?}}", + quote!{#ident}, quote!{#arg}, quote!{#ty}); + let give_err = LitStr::new(&give_err, Span::call_site()); + write_quote_to(&mut out, quote! { + #arg: ::tengri::dsl::Context::get(state, &mut iter) + .unwrap_or_else(||panic!(#give_err, token)), + }); + } + out + })); + } + out + } + fn to_enum_variant_unbind (&self) -> TokenStream2 { + let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); + //let ident = &self.0; + if self.has_args() { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for (arg, _ty) in self.args() { + write_quote_to(&mut out, quote! { #arg , }); + } + out + })); + } + out + } + fn to_matcher (&self) -> TokenStream2 { + let key = LitStr::new(&self.to_key(), Span::call_site()); + let variant = self.to_enum_variant_bind(); + let pattern = quote! { + Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) + }; + write_quote(quote! { + #pattern => { let mut iter = iter.clone(); Some(Self::#variant) }, + }) + } + fn to_implementation (&self) -> TokenStream2 { + let ident = &self.0; + let variant = self.to_enum_variant_unbind(); + let give_rest = write_quote(quote! { /*TODO*/ }); + let give_args = self.args() + .map(|(arg, _ty)|write_quote(quote! { #arg, })) + .collect::>(); + write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), }) + } } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index d6f23a8..4a11347 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,15 +85,15 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc]. - impl ::tengri::dsl::Dsl<#t> for #target { - fn take <'state, 'source> ( - &'state self, iter: &mut ::tengri::dsl::TokenIter<'source> - ) -> Perhaps<#t> { - Ok(Some(match iter.next().map(|x|x.value) { + impl<'state> ::tengri::dsl::Context<'state, #t> for #target { + fn get <'source> ( + &self, iter: &mut ::tengri::dsl::TokenIter<'source> + ) -> Option<#t> { + Some(match iter.next().map(|x|x.value) { #predefined #(#values,)* - _ => return Ok(None) - })) + _ => return None + }) } } }); diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 48af79f..7fdd1fd 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -44,7 +44,7 @@ impl Parse for ViewImpl { impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; - let view = &block.self_ty; + let ident = &block.self_ty; let mut available = vec![]; let exposed: Vec<_> = exposed.iter().map(|(k,v)|{ available.push(k.clone()); @@ -52,24 +52,23 @@ impl ToTokens for ViewDef { }).collect(); let available: String = available.join(", "); let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{token:?}}, available: {available}"), + &format!("expected Sym(content), got: {{value:?}}, available: {available}"), Span::call_site() ); for token in quote! { #block /// Generated by [tengri_proc]. - impl ::tengri::output::Content<#output> for #view { + impl ::tengri::output::Content<#output> for #ident { fn content (&self) -> impl Render<#output> { - self.view() + self.size.of(::tengri::output::View(self, self.config.view)) } } /// Generated by [tengri_proc]. - impl<'state> ::tengri::dsl::FromDsl<'state, #view> for ::tengri::output::RenderBox<'state, #output> { - fn take_from <'source: 'state> ( - state: &'state #view, - token: &mut ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { - Ok(match token.peek() { #(#exposed)* _ => None }) + impl<'state> ::tengri::output::ViewContext<'state, #output> for #ident { + fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) + -> Option> + { + match value { #(#exposed)* _ => panic!(#error_msg) } } } } { @@ -81,44 +80,21 @@ impl ToTokens for ViewDef { impl ToTokens for ViewArm { fn to_tokens (&self, out: &mut TokenStream2) { let Self(key, value) = self; - out.append(Ident::new("Some", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("tengri", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("dsl", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Value", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new("Sym", Span::call_site())); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("tengri", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("dsl", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("Token", Span::call_site())); - out.append(Group::new(Delimiter::Brace, { - let mut out = TokenStream2::new(); - out.append(Ident::new("value", Span::call_site())); - out.append(Punct::new(':', Alone)); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("tengri", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("dsl", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("Value", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new("Sym", Span::call_site())); - out.append(Group::new(Delimiter::Parenthesis, { - let mut out = TokenStream2::new(); - out.append(LitStr::new(key, Span::call_site()).token()); - out - })); - out.append(Punct::new(',', Alone)); - out.append(Punct::new('.', Joint)); - out.append(Punct::new('.', Alone)); - out - })); + out.append(LitStr::new(key, Span::call_site()).token()); out })); out.append(Punct::new('=', Joint)); @@ -126,7 +102,7 @@ impl ToTokens for ViewArm { out.append(Ident::new("Some", Span::call_site())); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); - out.append(Ident::new("state", Span::call_site())); + out.append(Ident::new("self", Span::call_site())); out.append(Punct::new('.', Alone)); out.append(value.clone()); out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index d451e70..25d7b27 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -7,7 +7,7 @@ pub use ::tengri_core::*; #[cfg(test)] extern crate tengri_proc; #[cfg(test)] #[test] fn test_subcommand () -> Usually<()> { use crate::input::{Command, InputMap, KeyMap, Handle, handle}; - use crate::dsl::TokenIter; + use crate::dsl::{TryFromDsl, TokenIter}; use crate::tui::TuiIn; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; //use crate::input::*; @@ -82,7 +82,7 @@ pub use ::tengri_core::*; //FIXME: //#[cfg(test)] #[test] fn test_dsl_context () { - //use crate::dsl::{Dsl, Value}; + //use crate::dsl::{Context, Value}; //struct Test; //#[tengri_proc::expose] @@ -91,10 +91,10 @@ pub use ::tengri_core::*; //true //} //} - //assert_eq!(Dsl::get(&Test, &Value::Sym(":false")), Some(false)); - //assert_eq!(Dsl::get(&Test, &Value::Sym(":true")), Some(true)); - //assert_eq!(Dsl::get(&Test, &Value::Sym(":some-bool")), Some(true)); - //assert_eq!(Dsl::get(&Test, &Value::Sym(":missing-bool")), None); - //assert_eq!(Dsl::get(&Test, &Value::Num(0)), Some(false)); - //assert_eq!(Dsl::get(&Test, &Value::Num(1)), Some(true)); + //assert_eq!(Context::get(&Test, &Value::Sym(":false")), Some(false)); + //assert_eq!(Context::get(&Test, &Value::Sym(":true")), Some(true)); + //assert_eq!(Context::get(&Test, &Value::Sym(":some-bool")), Some(true)); + //assert_eq!(Context::get(&Test, &Value::Sym(":missing-bool")), None); + //assert_eq!(Context::get(&Test, &Value::Num(0)), Some(false)); + //assert_eq!(Context::get(&Test, &Value::Num(1)), Some(true)); //} diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 0d563fd..38960b9 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -21,30 +21,6 @@ impl> Content for std::sync::Arc { } } -impl> Content for Result> { - fn content (&self) -> impl Render { - Bsp::a(self.as_ref().ok(), self.as_ref().err() - .map(|e|Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(32,32,32), e.to_string()))) - } -} - -//impl> Render for Result> { - //fn layout (&self, to: [u16;4]) -> [u16;4] { - //match self { - //Ok(content) => content.layout(to), - //Err(e) => [0, 0, to.w(), to.h()] - //} - //} - //fn render (&self, to: &mut TuiOut) { - //match self { - //Ok(content) => content.render(to), - //Err(e) => to.blit(&e.to_string(), 0, 0, Some(Style::default() - //.bg(Color::Rgb(32,32,32)) - //.fg(Color::Rgb(255,255,255)))) - //} - //} -//} - mod tui_border; pub use self::tui_border::*; mod tui_button; pub use self::tui_button::*; mod tui_color; pub use self::tui_color::*;