From a46d0d225819ce518f7064ddfe464f47ce71aa36 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 21 Jun 2025 13:48:45 +0300 Subject: [PATCH] wip: fix(dsl): maybe getting somewhere? --- core/src/lib.rs | 4 +- dsl/src/ast.rs | 35 ++- dsl/src/cst.rs | 45 +-- dsl/src/dsl.rs | 137 +++++---- dsl/src/lib.rs | 6 +- input/src/input_dsl.rs | 29 +- output/src/lib.rs | 178 +----------- output/src/ops.rs | 318 ++++++++++++++++++++- output/src/ops/{collect.rs => _collect.rs} | 0 output/src/ops/{reduce.rs => _reduce.rs} | 0 output/src/ops/transform.rs | 192 ------------- proc/src/proc_command.rs | 14 +- proc/src/proc_expose.rs | 18 +- proc/src/proc_view.rs | 23 +- tengri/src/test.rs | 18 +- tui/examples/tui.rs | 2 +- 16 files changed, 506 insertions(+), 513 deletions(-) rename output/src/ops/{collect.rs => _collect.rs} (100%) rename output/src/ops/{reduce.rs => _reduce.rs} (100%) delete mode 100644 output/src/ops/transform.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 39d7e6c..cc04597 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -46,9 +46,9 @@ pub trait MaybeHas: Send + Sync { /// May compute a `RetVal` from `Args`. pub trait Eval { /// A custom operation on [Args] that may return [Result::Err] or [Option::None]. - fn try_eval (&self, args: Args) -> Perhaps; + fn try_eval (&self, args: &Args) -> Perhaps; /// Invoke a custom operation, converting a `None` result to a custom `Box`. - fn eval >> (&self, args: Args, error: impl Fn()->E) + fn eval >> (&self, args: &Args, error: impl Fn()->E) -> Usually { match self.try_eval(args)? { diff --git a/dsl/src/ast.rs b/dsl/src/ast.rs index be188f0..02f7071 100644 --- a/dsl/src/ast.rs +++ b/dsl/src/ast.rs @@ -1,14 +1,20 @@ use crate::*; +/// The abstract syntax tree (AST) is produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. #[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub AstValue); +pub struct Ast(pub DslVal, VecDeque>); -/// The abstract syntax tree (AST) can be produced from the CST -/// by cloning source slices into owned [Arc] values. -pub type AstValue = DslValue, VecDeque>; - -//#[derive(Debug, Clone, Default, PartialEq)] -//pub struct AstIter(); +impl Dsl for Ast { + type Str = Arc; + type Exp = VecDeque; + fn val (&self) -> &DslVal { + self.0.val() + } + fn nth (&self, index: usize) -> Option<&DslVal> { + todo!() + } +} impl<'src> From> for Ast { fn from (token: Cst<'src>) -> Self { @@ -18,14 +24,15 @@ impl<'src> From> for Ast { impl<'src> From> for Ast { fn from (value: CstValue<'src>) -> Self { + use DslVal::*; Self(match value { - DslValue::Nil => DslValue::Nil, - DslValue::Err(e) => DslValue::Err(e), - DslValue::Num(u) => DslValue::Num(u), - DslValue::Sym(s) => DslValue::Sym(s.into()), - DslValue::Key(s) => DslValue::Key(s.into()), - DslValue::Str(s) => DslValue::Str(s.into()), - DslValue::Exp(d, x) => DslValue::Exp(d, x.map(|x|x.into()).collect()), + Nil => Nil, + Err(e) => Err(e), + Num(u) => Num(u), + Sym(s) => Sym(s.into()), + Key(s) => Key(s.into()), + Str(s) => Str(s.into()), + Exp(d, x) => Exp(d, x.map(|x|x.into()).collect()), }) } } diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs index 3dff71f..8424f22 100644 --- a/dsl/src/cst.rs +++ b/dsl/src/cst.rs @@ -31,7 +31,7 @@ macro_rules! iterate { } /// CST stores strings as source references and expressions as [SourceIter] instances. -pub type CstValue<'source> = DslValue<&'source str, SourceIter<'source>>; +pub type CstValue<'src> = DslVal<&'src str, SourceIter<'src>>; /// Token sharing memory with source reference. #[derive(Debug, Copy, Clone, Default, PartialEq)] @@ -46,6 +46,17 @@ pub struct Cst<'src> { pub value: CstValue<'src>, } +impl<'src> Dsl for Cst<'src> { + type Str = &'src str; + type Exp = SourceIter<'src>; + fn val (&self) -> &DslVal<&'src str, SourceIter<'src>> { + &self.value + } + fn nth (&self, index: usize) -> Option<&DslVal<&'src str, SourceIter<'src>>> { + todo!() + } +} + impl<'src> Cst<'src> { pub const fn new ( source: &'src str, @@ -73,51 +84,51 @@ impl<'src> Cst<'src> { pub const fn value (&self) -> CstValue<'src> { self.value } - pub const fn error (self, error: DslError) -> Self { - Self { value: DslValue::Err(error), ..self } + pub const fn error (self, error: DslErr) -> Self { + Self { value: DslVal::Err(error), ..self } } pub const fn grow (self) -> Self { Self { length: self.length.saturating_add(1), ..self } } pub const fn grow_num (self, m: usize, c: char) -> Self { match to_digit(c) { - Result::Ok(n) => Self { value: DslValue::Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: DslValue::Err(e), ..self.grow() }, + Result::Ok(n) => Self { value: DslVal::Num(10*m+n), ..self.grow() }, + Result::Err(e) => Self { value: DslVal::Err(e), ..self.grow() }, } } pub const fn grow_key (self) -> Self { let token = self.grow(); - token.with_value(DslValue::Key(token.slice_source(self.source))) + token.with_value(DslVal::Key(token.slice_source(self.source))) } pub const fn grow_sym (self) -> Self { let token = self.grow(); - token.with_value(DslValue::Sym(token.slice_source(self.source))) + token.with_value(DslVal::Sym(token.slice_source(self.source))) } pub const fn grow_str (self) -> Self { let token = self.grow(); - token.with_value(DslValue::Str(token.slice_source(self.source))) + token.with_value(DslVal::Str(token.slice_source(self.source))) } pub const fn grow_exp (self) -> Self { let token = self.grow(); - if let DslValue::Exp(depth, _) = token.value() { - token.with_value(DslValue::Exp(depth, SourceIter::new(token.slice_source_exp(self.source)))) + if let DslVal::Exp(depth, _) = token.value() { + token.with_value(DslVal::Exp(depth, SourceIter::new(token.slice_source_exp(self.source)))) } else { unreachable!() } } pub const fn grow_in (self) -> Self { let token = self.grow_exp(); - if let DslValue::Exp(depth, source) = token.value() { - token.with_value(DslValue::Exp(depth.saturating_add(1), source)) + if let DslVal::Exp(depth, source) = token.value() { + token.with_value(DslVal::Exp(depth.saturating_add(1), source)) } else { unreachable!() } } pub const fn grow_out (self) -> Self { let token = self.grow_exp(); - if let DslValue::Exp(depth, source) = token.value() { + if let DslVal::Exp(depth, source) = token.value() { if depth > 0 { - token.with_value(DslValue::Exp(depth - 1, source)) + token.with_value(DslVal::Exp(depth - 1, source)) } else { return self.error(Unexpected(')')) } @@ -145,7 +156,7 @@ pub const fn to_digit (c: char) -> DslResult { } pub const fn peek_src <'src> (source: &'src str) -> Option> { - use DslValue::*; + use DslVal::*; let mut token: Cst<'src> = Cst::new(source, 0, 0, Nil); iterate!(char_indices(source) => (start, c) => token = match token.value() { Err(_) => return Some(token), @@ -162,8 +173,8 @@ pub const fn peek_src <'src> (source: &'src str) -> Option> { Cst::new(source, start, 1, Key(str_range(source, start, start + 1))), '0'..='9' => Cst::new(source, start, 1, match to_digit(c) { - Ok(c) => DslValue::Num(c), - Result::Err(e) => DslValue::Err(e) + Ok(c) => DslVal::Num(c), + Result::Err(e) => DslVal::Err(e) }), _ => token.error(Unexpected(c)) }, diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 615804d..d46bb46 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -1,6 +1,10 @@ use crate::*; -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslError { +/// Standard result type for DSL-specific operations. +pub type DslResult = Result; + +/// DSL-specific error codes. +#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslErr { #[error("parse failed: not implemented")] Unimplemented, #[error("parse failed: empty")] @@ -13,41 +17,13 @@ use crate::*; Code(u8), } -/// Thing that may construct itself from `State` and [DslValue]. -pub trait FromDsl: Sized { - fn try_provide ( - state: &State, - value: DslValue - ) -> Perhaps; - fn provide ( - state: &State, - value: DslValue, - error: impl Fn()->Box - ) -> Usually { - match Self::try_provide(state, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -pub type DslResult = Result; - -/// Marker trait for supported string types. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef {} -impl> DslStr for T {} - -/// Marker trait for supported expression types. -pub trait DslExp: PartialEq + Clone + Default + Debug {} -impl DslExp for T {} - -/// A DSL value generic over string and expression types. -/// See [CstValue] and [AstValue]. +/// Enumeration of possible DSL tokens. +/// Generic over string and expression storage. #[derive(Clone, Debug, PartialEq, Default)] -pub enum DslValue { +pub enum DslVal { #[default] Nil, - Err(DslError), + Err(DslErr), Num(usize), Sym(Str), Key(Str), @@ -55,38 +31,67 @@ pub enum DslValue { Exp(usize, Exp), } -impl DslValue { +pub trait Dsl { + type Str: PartialEq + Clone + Default + Debug + AsRef; + type Exp: PartialEq + Clone + Default + Debug; + fn val (&self) -> &DslVal; + fn nth (&self, index: usize) -> Option<&DslVal>; + // exp-only nth here? +} + +impl< + Str: PartialEq + Clone + Default + Debug + AsRef, + Exp: PartialEq + Clone + Default + Debug, +> Dsl for DslVal { + type Str = Str; + type Exp = Exp; + fn val (&self) -> &DslVal { + self + } + fn nth (&self, index: usize) -> Option<&DslVal> { + todo!() + } +} + +/// May construct self from state and DSL. +pub trait DslFrom: Sized { + fn try_dsl_from (state: &State, value: &impl Dsl) -> Perhaps; + fn dsl_from ( + state: &State, value: &impl Dsl, error: impl Fn()->Box + ) -> Usually { + match Self::try_dsl_from(state, value)? { + Some(value) => Ok(value), + _ => Err(error()) + } + } +} + +/// May construct another from self and DSL. +pub trait DslInto { + fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into ( + &self, value: &impl Dsl, error: impl Fn()->Box + ) -> Usually { + match Self::try_dsl_into(self, value)? { + Some(value) => Ok(value), + _ => Err(error()) + } + } +} + +impl DslVal { pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } - pub fn as_err (&self) -> Option<&DslError> { + pub fn as_err (&self) -> Option<&DslErr> { if let Self::Err(e) = self { Some(e) } else { None } } pub fn as_num (&self) -> Option { if let Self::Num(n) = self { Some(*n) } else { None } } - pub fn as_sym (&self) -> Option<&str> { - if let Self::Sym(s) = self { Some(s.as_ref()) } else { None } - } - pub fn as_key (&self) -> Option<&str> { - if let Self::Key(k) = self { Some(k.as_ref()) } else { None } - } - pub fn as_str (&self) -> Option<&str> { - if let Self::Str(s) = self { Some(s.as_ref()) } else { None } - } pub fn as_exp (&self) -> Option<&Exp> { if let Self::Exp(_, x) = self { Some(x) } else { None } } - pub fn exp_match (&self, namespace: &str, cb: F) -> Perhaps - where F: Fn(&str, &Exp)-> Perhaps { - if let Some(Self::Key(key)) = self.exp_head() - && key.as_ref().starts_with(namespace) - && let Some(tail) = self.exp_tail() { - cb(key.as_ref().split_at(namespace.len()).1, tail) - } else { - Ok(None) - } - } pub fn exp_depth (&self) -> Option { todo!() } @@ -107,4 +112,26 @@ impl DslValue { } } -impl Copy for DslValue {} +impl, Exp> DslVal { + pub fn as_sym (&self) -> Option<&str> { + if let Self::Sym(s) = self { Some(s.as_ref()) } else { None } + } + pub fn as_key (&self) -> Option<&str> { + if let Self::Key(k) = self { Some(k.as_ref()) } else { None } + } + pub fn as_str (&self) -> Option<&str> { + if let Self::Str(s) = self { Some(s.as_ref()) } else { None } + } + pub fn exp_match (&self, namespace: &str, cb: F) -> Perhaps + where F: Fn(&str, &Exp)-> Perhaps { + if let Some(Self::Key(key)) = self.exp_head() + && key.as_ref().starts_with(namespace) + && let Some(tail) = self.exp_tail() { + cb(key.as_ref().split_at(namespace.len()).1, tail) + } else { + Ok(None) + } + } +} + +impl Copy for DslVal {} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 45fb178..3558cfd 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -41,7 +41,7 @@ pub(crate) use std::collections::VecDeque; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; -pub(crate) use self::DslError::*; +pub(crate) use self::DslErr::*; mod dsl; pub use self::dsl::*; mod ast; pub use self::ast::*; @@ -96,7 +96,7 @@ mod cst; pub use self::cst::*; //} #[cfg(test)] #[test] fn test_token () -> Result<(), Box> { - use crate::DslValue::*; + use crate::DslVal::*; let source = ":f00"; let mut token = Cst::new(source, 0, 1, Sym(":")); token = token.grow_sym(); @@ -126,7 +126,7 @@ mod cst; pub use self::cst::*; Ok(()) } -//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { +//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslErr> { //// Let's pretend to render some view. //let source = include_str!("../../tek/src/view_arranger.edn"); //// The token iterator allows you to get the tokens represented by the source text. diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index dba8bd3..dfb156e 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,51 +5,52 @@ use crate::*; /// Each contained layer defines a mapping from input event to command invocation /// over a given state. Furthermore, each layer may have an associated condition, /// so that only certain layers are active at a given time depending on state. -#[derive(Default, Debug)] pub struct InputLayers(Vec); +#[derive(Debug)] pub struct InputLayers(Vec>); +impl Default for InputLayers { fn default () -> Self { Self(vec![]) } } /// A single input binding layer. -#[derive(Default, Debug)] struct InputLayer { condition: Option, bindings: Ast, } +#[derive(Default, Debug)] struct InputLayer { condition: Option, bindings: T, } -impl InputLayers { +impl InputLayers { /// Create an input map with a single non-conditional layer. /// (Use [Default::default] to get an empty map.) - pub fn new (layer: Ast) -> Self { + pub fn new (layer: T) -> Self { Self::default().layer(layer) } /// Add layer, return `Self`. - pub fn layer (mut self, layer: Ast) -> Self { + pub fn layer (mut self, layer: T) -> Self { self.add_layer(layer); self } /// Add conditional layer, return `Self`. - pub fn layer_if (mut self, condition: Ast, layer: Ast) -> Self { + pub fn layer_if (mut self, condition: T, layer: T) -> Self { self.add_layer_if(Some(condition), layer); self } /// Add layer, return `&mut Self`. - pub fn add_layer (&mut self, layer: Ast) -> &mut Self { + pub fn add_layer (&mut self, layer: T) -> &mut Self { self.add_layer_if(None, layer.into()); self } /// Add conditional layer, return `&mut Self`. - pub fn add_layer_if (&mut self, condition: Option, bindings: Ast) -> &mut Self { + pub fn add_layer_if (&mut self, condition: Option, bindings: T) -> &mut Self { self.0.push(InputLayer { condition, bindings }); self } /// Evaluate the active layers for a given state, /// returning the command to be executed, if any. pub fn handle (&self, state: &mut S, input: I) -> Perhaps where - S: Eval + Eval, - I: Eval, + S: DslInto + DslInto, + I: DslInto, O: Command { let layers = self.0.as_slice(); for InputLayer { condition, bindings } in layers.iter() { let mut matches = true; if let Some(condition) = condition { - matches = state.eval(condition.clone(), ||"input: no condition")?; + matches = state.dsl_into(condition, ||format!("input: no condition").into())?; } if matches - && let Some(exp) = bindings.0.exp_head() - && input.eval(Ast(exp.clone()), ||"InputLayers: input.eval(binding) failed")? - && let Some(command) = state.try_eval(exp)? { + && let Some(exp) = bindings.val().exp_head() + && input.dsl_into(exp, ||format!("InputLayers: input.eval(binding) failed").into())? + && let Some(command) = state.try_dsl_into(exp)? { return Ok(Some(command)) } } diff --git a/output/src/lib.rs b/output/src/lib.rs index e8fdb72..45abdda 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -1,180 +1,14 @@ #![feature(step_trait)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] -pub(crate) use tengri_core::*; + pub(crate) use std::marker::PhantomData; +pub(crate) use tengri_core::*; +#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; + mod space; pub use self::space::*; mod ops; pub use self::ops::*; mod output; pub use self::output::*; -#[cfg(test)] mod test; -#[cfg(test)] pub use proptest_derive::Arbitrary; -/// Enabling the `dsl` feature implements [FromDsl] for -/// the layout elements that are provided by this crate. -#[cfg(feature = "dsl")] mod ops_dsl { - use crate::*; - use ::tengri_dsl::*; - /// The syntagm `(when :condition :content)` corresponds to a [When] layout element. - impl FromDsl for When where S: Eval + Eval { - fn try_provide (state: &S, source: DslValue) -> Perhaps { - source.exp_match("when", |_, tail|Ok(Some(Self( - tail.eval(0, ||"no condition")?, - tail.eval(1, ||"no content")?, - )))) - } - } - /// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element. - impl FromDsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: &S, source: DslValue) -> Perhaps { - source.exp_match("either", |_, tail|Ok(Some(Self( - tail.eval(0, ||"no condition")?, - tail.eval(1, ||"no content 1")?, - tail.eval(2, ||"no content 2")?, - )))) - } - } - /// The syntagm `(align/* :content)` corresponds to an [Align] layout element, - /// where `*` specifies the direction of the alignment. - impl FromDsl for Align where S: Eval, A> { - fn try_provide (state: &S, source: DslValue) -> Perhaps { - source.exp_match("align/", |head, tail|Ok(Some(match head { - "c" => Self::c(tail.eval(0, ||"no content")?), - "x" => Self::x(tail.eval(0, ||"no content")?), - "y" => Self::y(tail.eval(0, ||"no content")?), - "n" => Self::n(tail.eval(0, ||"no content")?), - "s" => Self::s(tail.eval(0, ||"no content")?), - "e" => Self::e(tail.eval(0, ||"no content")?), - "w" => Self::w(tail.eval(0, ||"no content")?), - "nw" => Self::nw(tail.eval(0, ||"no content")?), - "ne" => Self::ne(tail.eval(0, ||"no content")?), - "sw" => Self::sw(tail.eval(0, ||"no content")?), - "se" => Self::se(tail.eval(0, ||"no content")?), - _ => return Err("invalid align variant".into()) - }))) - } - } - /// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element, - /// where `*` specifies the direction of the split. - impl FromDsl for Bsp where S: Eval, A> + Eval, B> { - fn try_provide (state: &S, source: DslValue) -> Perhaps { - source.exp_match("bsp/", |head, tail|Ok(Some(match head { - "n" => Self::n(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - "s" => Self::s(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - "e" => Self::e(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - "w" => Self::w(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - "a" => Self::a(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - "b" => Self::b(tail.eval(0, ||"no content 1"), tail.eval(1, ||"no content 2")), - _ => return Ok(None), - }))) - } - } - //#[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( - //if let Some(Token { value: Key(k), .. }) = words.peek() { - //let mut base = words.clone(); - //let content = state.give_or_fail(words, ||format!("{k}: no content"))?; - //return Ok(Some(match words.next() { - //Some(Token{value: Key($x),..}) => Self::x(content), - //Some(Token{value: Key($y),..}) => Self::y(content), - //Some(Token{value: Key($xy),..}) => Self::xy(content), - //_ => unreachable!() - //})) - //} else { - //None - //})); - //#[cfg(feature = "dsl")] take!($Enum, U, A|state, words|Ok( - //if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() { - //let mut base = words.clone(); - //Some(match words.next() { - //Some(Token { value: Key($x), .. }) => Self::x( - //state.give_or_fail(words, ||"x: no unit")?, - //state.give_or_fail(words, ||"x: no content")?, - //), - //Some(Token { value: Key($y), .. }) => Self::y( - //state.give_or_fail(words, ||"y: no unit")?, - //state.give_or_fail(words, ||"y: no content")?, - //), - //Some(Token { value: Key($x), .. }) => Self::xy( - //state.give_or_fail(words, ||"xy: no unit x")?, - //state.give_or_fail(words, ||"xy: no unit y")?, - //state.give_or_fail(words, ||"xy: no content")? - //), - //_ => unreachable!(), - //}) - //} else { - //None - //})); - //if let Exp(_, exp) = source.value() { - //let mut rest = exp.clone(); - //return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { - //Some("bsp/n") => Self::n( - //state.eval(rest.next(), ||"bsp/n: no content 1")?, - //state.eval(rest.next(), ||"bsp/n: no content 2")?, - //), - //Some("bsp/s") => Self::s( - //state.eval(rest.next(), ||"bsp/s: no content 1")?, - //state.eval(rest.next(), ||"bsp/s: no content 2")?, - //), - //Some("bsp/e") => Self::e( - //state.eval(rest.next(), ||"bsp/e: no content 1")?, - //state.eval(rest.next(), ||"bsp/e: no content 2")?, - //), - //Some("bsp/w") => Self::w( - //state.eval(rest.next(), ||"bsp/w: no content 1")?, - //state.eval(rest.next(), ||"bsp/w: no content 2")?, - //), - //Some("bsp/a") => Self::a( - //state.eval(rest.next(), ||"bsp/a: no content 1")?, - //state.eval(rest.next(), ||"bsp/a: no content 2")?, - //), - //Some("bsp/b") => Self::b( - //state.eval(rest.next(), ||"bsp/b: no content 1")?, - //state.eval(rest.next(), ||"bsp/b: no content 2")?, - //), - //_ => return Ok(None), - //})) - //} - //Ok(None) - //if let Exp(_, source) = source.value() { - //let mut rest = source.clone(); - //return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { - //Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: no content")?), - //Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: no content")?), - //Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: no content")?), - //Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: no content")?), - //Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: no content")?), - //Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: no content")?), - //Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: no content")?), - //Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: no content")?), - //Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: no content")?), - //Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: no content")?), - //Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: no content")?), - //_ => return Ok(None), - //})) - //} - //Ok(None) - //Ok(match source.exp_head().and_then(|e|e.key()) { - //Some("either") => Some(Self( - //source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?, - //source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content 1"))?, - //source.exp_tail().and_then(|t|t.get(2)).map(|x|state.eval(x, ||"when: no content 2"))?, - //)), - //_ => None - //}) - //if let Exp(_, mut exp) = source.value() - //&& let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { - //let _ = exp.next(); - //return Ok(Some(Self( - //state.eval(exp.next().unwrap(), ||"either: no condition")?, - //state.eval(exp.next().unwrap(), ||"either: no content 1")?, - //state.eval(exp.next().unwrap(), ||"either: no content 2")?, - //))) - //} - //Ok(None) - //Ok(match source.exp_head().and_then(|e|e.key()) { - //Some("when") => Some(Self( - //source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?, - //source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content"))?, - //)), - //_ => None - //}) -} +#[cfg(test)] mod test; +#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; diff --git a/output/src/ops.rs b/output/src/ops.rs index 88a2ccb..9c528f2 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -49,11 +49,10 @@ use crate::*; use Direction::*; -mod map; pub use self::map::*; -mod memo; pub use self::memo::*; -mod stack; pub use self::stack::*; -mod thunk; pub use self::thunk::*; -mod transform; //pub use self::transform::*; +mod map; pub use self::map::*; +mod memo; pub use self::memo::*; +mod stack; pub use self::stack::*; +mod thunk; pub use self::thunk::*; /// Renders multiple things on top of each other, #[macro_export] macro_rules! lay { @@ -223,7 +222,7 @@ pub trait BspAreas, B: Content> { let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); let a = [(x + w/2.into()).minus(aw/2.into()), y, aw, ah]; let b = [(x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] + [a.into(), b.into(), [x, y, w, h].into()] }, North => { let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); @@ -380,3 +379,310 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ let dy = self.dy(); [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] }); + +/// Enabling the `dsl` feature implements [DslFrom] for +/// the layout elements that are provided by this crate. +#[cfg(feature = "dsl")] mod ops_dsl { + use crate::*; + use ::tengri_dsl::*; + + //macro_rules! dsl { + //($( + //($Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$($arg:ident $(:$ty:ty)?),*] $expr:expr) + //)*) => { + //$( + //impl DslFrom for $Struct$(<$($A),+>)? { + //fn try_dsl_from ( + //state: &S, dsl: &impl Dsl + //) -> Perhaps { + //todo!() + //} + //} + //)* + //} + //} + + macro_rules! dsl { + ( + $Struct:ident $(<$($A:ident),+>)? + $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr + ) => { + impl DslFrom for $Struct$(<$($A),+>)? { + fn try_dsl_from ( + _state: &S, _dsl: &impl Dsl + ) -> Perhaps { + todo!() + } + } + } + } + + macro_rules! dsl_ns { + ( + $Struct:ident $(<$($A:ident),+>)? + $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr + ) => { + impl DslFrom for $Struct$(<$($A),+>)? { + fn try_dsl_from ( + _state: &S, _dsl: &impl Dsl + ) -> Perhaps { + todo!() + } + } + } + } + + dsl!(When "when" [_head, tail] Self(tail(0)?, tail(1)?)); + + dsl!(Either "either" [_head, tail] Self(tail(0)?, tail(1)?, tail(2)?)); + + dsl_ns!(Align "align/" [head, tail] match head { + "c" => Self::c(tail(0)?), + "x" => Self::x(tail(0)?), + "y" => Self::y(tail(0)?), + "n" => Self::n(tail(0)?), + "s" => Self::s(tail(0)?), + "e" => Self::e(tail(0)?), + "w" => Self::w(tail(0)?), + "nw" => Self::nw(tail(0)?), + "ne" => Self::ne(tail(0)?), + "sw" => Self::sw(tail(0)?), + "se" => Self::se(tail(0)?), + _ => return Err("invalid align variant".into()) }); + + dsl_ns!(Bsp "bsp/" [head, tail] match head { + "n" => Self::n(tail(0)?, tail(1)?), + "s" => Self::s(tail(0)?, tail(1)?), + "e" => Self::e(tail(0)?, tail(1)?), + "w" => Self::w(tail(0)?, tail(1)?), + "a" => Self::a(tail(0)?, tail(1)?), + "b" => Self::b(tail(0)?, tail(1)?), + _ => return Err("invalid bsp variant".into()) }); + + dsl_ns!(Fill "fill/" [head, tail] match x { + "x" => Self::x(tail(0)?), + "y" => Self::y(tail(0)?), + "xy" => Self::xy(tail(0)?), + _ => return Err("invalid fill variant".into()) }); + + dsl_ns!(Fixed "fixed/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid fill variant".into()) }); + + dsl_ns!(Min "min/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid min variant".into()) }); + + dsl_ns!(Max "max/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + dsl_ns!(Shrink "shrink/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid min variant".into()) }); + + dsl_ns!(Expand "expand/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + dsl_ns!(Pull "pull/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + dsl_ns!(Push "push/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + dsl_ns!(Margin "margin/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + dsl_ns!(Padding "padding/" [head, tail] match x { + "x" => Self::x(tail(0)?, tail(1)?), + "y" => Self::y(tail(0)?, tail(1)?), + "xy" => Self::xy(tail(0)?, tail(1)?, tail(2)?), + _ => return Err("invalid max variant".into()) }); + + + ///// The syntagm `(when :condition :content)` corresponds to a [When] layout element. + //impl FromDsl for When where bool: FromDsl, A: FromDsl { + //fn try_provide (state: &S, source: &DslVal) -> Perhaps { + //source.exp_match("when", |_, tail|Ok(Some(Self( + //FromDsl::::provide(state, + //tail.nth(0, ||"no condition".into())?, ||"no condition".into())?, + //FromDsl::::provide(state, + //tail.nth(1, ||"no content".into())?, ||"no content".into())?, + //)))) + //} + //} + ///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element. + //impl FromDsl for Either where S: Eval + Eval + Eval { + //fn try_provide (state: &S, source: &DslVal) -> Perhaps { + //source.exp_match("either", |_, tail|Ok(Some(Self( + //state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?, + //state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?, + //state.eval(tail.nth(2, ||"no content 1")?, ||"no content 2")?, + //)))) + //} + //} + ///// The syntagm `(align/* :content)` corresponds to an [Align] layout element, + ///// where `*` specifies the direction of the alignment. + //impl FromDsl for Align where S: Eval, A> { + //fn try_provide (state: &S, source: &DslVal) -> Perhaps { + //source.exp_match("align/", |head, tail|Ok(Some(match head { + //"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"y" => Self::y(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"n" => Self::n(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"s" => Self::s(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"e" => Self::e(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"w" => Self::w(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"nw" => Self::nw(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"ne" => Self::ne(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"sw" => Self::sw(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //"se" => Self::se(state.eval(tail.nth(0, ||"no content")?, ||"no content")), + //_ => return Err("invalid align variant".into()) + //}))) + //} + //} + ///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element, + ///// where `*` specifies the direction of the split. + //impl FromDsl for Bsp where S: Eval, A> + Eval, B> { + //fn try_provide (state: &S, source: &DslVal) -> Perhaps { + //source.exp_match("bsp/", |head, tail|Ok(Some(match head { + //"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //"e" => Self::e(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //"w" => Self::w(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //"a" => Self::a(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //"b" => Self::b(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), + //_ => return Ok(None), + //}))) + //} + //} + //#[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( + //if let Some(Token { value: Key(k), .. }) = words.peek() { + //let mut base = words.clone(); + //let content = state.give_or_fail(words, ||format!("{k}: no content"))?; + //return Ok(Some(match words.next() { + //Some(Token{value: Key($x),..}) => Self::x(content), + //Some(Token{value: Key($y),..}) => Self::y(content), + //Some(Token{value: Key($xy),..}) => Self::xy(content), + //_ => unreachable!() + //})) + //} else { + //None + //})); + //#[cfg(feature = "dsl")] take!($Enum, U, A|state, words|Ok( + //if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() { + //let mut base = words.clone(); + //Some(match words.next() { + //Some(Token { value: Key($x), .. }) => Self::x( + //state.give_or_fail(words, ||"x: no unit")?, + //state.give_or_fail(words, ||"x: no content")?, + //), + //Some(Token { value: Key($y), .. }) => Self::y( + //state.give_or_fail(words, ||"y: no unit")?, + //state.give_or_fail(words, ||"y: no content")?, + //), + //Some(Token { value: Key($x), .. }) => Self::xy( + //state.give_or_fail(words, ||"xy: no unit x")?, + //state.give_or_fail(words, ||"xy: no unit y")?, + //state.give_or_fail(words, ||"xy: no content")? + //), + //_ => unreachable!(), + //}) + //} else { + //None + //})); + //if let Exp(_, exp) = source.value() { + //let mut rest = exp.clone(); + //return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { + //Some("bsp/n") => Self::n( + //state.eval(rest.next(), ||"bsp/n: no content 1")?, + //state.eval(rest.next(), ||"bsp/n: no content 2")?, + //), + //Some("bsp/s") => Self::s( + //state.eval(rest.next(), ||"bsp/s: no content 1")?, + //state.eval(rest.next(), ||"bsp/s: no content 2")?, + //), + //Some("bsp/e") => Self::e( + //state.eval(rest.next(), ||"bsp/e: no content 1")?, + //state.eval(rest.next(), ||"bsp/e: no content 2")?, + //), + //Some("bsp/w") => Self::w( + //state.eval(rest.next(), ||"bsp/w: no content 1")?, + //state.eval(rest.next(), ||"bsp/w: no content 2")?, + //), + //Some("bsp/a") => Self::a( + //state.eval(rest.next(), ||"bsp/a: no content 1")?, + //state.eval(rest.next(), ||"bsp/a: no content 2")?, + //), + //Some("bsp/b") => Self::b( + //state.eval(rest.next(), ||"bsp/b: no content 1")?, + //state.eval(rest.next(), ||"bsp/b: no content 2")?, + //), + //_ => return Ok(None), + //})) + //} + //Ok(None) + //if let Exp(_, source) = source.value() { + //let mut rest = source.clone(); + //return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { + //Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: no content")?), + //Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: no content")?), + //Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: no content")?), + //Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: no content")?), + //Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: no content")?), + //Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: no content")?), + //Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: no content")?), + //Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: no content")?), + //Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: no content")?), + //Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: no content")?), + //Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: no content")?), + //_ => return Ok(None), + //})) + //} + //Ok(None) + //Ok(match source.exp_head().and_then(|e|e.key()) { + //Some("either") => Some(Self( + //source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?, + //source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content 1"))?, + //source.exp_tail().and_then(|t|t.get(2)).map(|x|state.eval(x, ||"when: no content 2"))?, + //)), + //_ => None + //}) + //if let Exp(_, mut exp) = source.value() + //&& let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { + //let _ = exp.next(); + //return Ok(Some(Self( + //state.eval(exp.next().unwrap(), ||"either: no condition")?, + //state.eval(exp.next().unwrap(), ||"either: no content 1")?, + //state.eval(exp.next().unwrap(), ||"either: no content 2")?, + //))) + //} + //Ok(None) + //Ok(match source.exp_head().and_then(|e|e.key()) { + //Some("when") => Some(Self( + //source.exp_tail().and_then(|t|t.get(0)).map(|x|state.eval(x, ||"when: no condition"))?, + //source.exp_tail().and_then(|t|t.get(1)).map(|x|state.eval(x, ||"when: no content"))?, + //)), + //_ => None + //}) +} diff --git a/output/src/ops/collect.rs b/output/src/ops/_collect.rs similarity index 100% rename from output/src/ops/collect.rs rename to output/src/ops/_collect.rs diff --git a/output/src/ops/reduce.rs b/output/src/ops/_reduce.rs similarity index 100% rename from output/src/ops/reduce.rs rename to output/src/ops/_reduce.rs diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs deleted file mode 100644 index 415c416..0000000 --- a/output/src/ops/transform.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! [Content] items that modify the inherent -//! dimensions of their inner [Render]ables. -//! -//! Transform may also react to the [Area] taked. -//! ``` -//! use ::tengri::{output::*, tui::*}; -//! let area: [u16;4] = [10, 10, 20, 20]; -//! fn test (area: [u16;4], item: &impl Content, expected: [u16;4]) { -//! assert_eq!(Content::layout(item, area), expected); -//! assert_eq!(Render::layout(item, area), expected); -//! }; -//! test(area, &(), [20, 20, 0, 0]); -//! -//! test(area, &Fill::xy(()), area); -//! test(area, &Fill::x(()), [10, 20, 20, 0]); -//! test(area, &Fill::y(()), [20, 10, 0, 20]); -//! -//! //FIXME:test(area, &Fixed::x(4, ()), [18, 20, 4, 0]); -//! //FIXME:test(area, &Fixed::y(4, ()), [20, 18, 0, 4]); -//! //FIXME:test(area, &Fixed::xy(4, 4, unit), [18, 18, 4, 4]); -//! ``` - -use crate::*; - -/// Defines an enum that transforms its content -/// along either the X axis, the Y axis, or both. -macro_rules! transform_xy { - ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => { - pub enum $Enum { X(A), Y(A), XY(A) } - impl $Enum { - #[inline] pub const fn x (item: A) -> Self { Self::X(item) } - #[inline] pub const fn y (item: A) -> Self { Self::Y(item) } - #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } - } - //#[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( - //if let Some(Token { value: Value::Key(k), .. }) = words.peek() { - //let mut base = words.clone(); - //let content = state.give_or_fail(words, ||format!("{k}: no content"))?; - //return Ok(Some(match words.next() { - //Some(Token{value: Value::Key($x),..}) => Self::x(content), - //Some(Token{value: Value::Key($y),..}) => Self::y(content), - //Some(Token{value: Value::Key($xy),..}) => Self::xy(content), - //_ => unreachable!() - //})) - //} else { - //None - //})); - impl> Content for $Enum { - fn content (&self) -> impl Render + '_ { - match self { - Self::X(item) => item, - Self::Y(item) => item, - Self::XY(item) => item, - } - } - fn layout (&$self, $to: ::Area) -> ::Area { - use $Enum::*; - $area - } - } - } -} - -/// Defines an enum that parametrically transforms its content -/// along either the X axis, the Y axis, or both. -macro_rules! transform_xy_unit { - ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$layout:expr) => { - pub enum $Enum { X(U, A), Y(U, A), XY(U, U, A), } - impl $Enum { - #[inline] pub const fn x (x: U, item: A) -> Self { Self::X(x, item) } - #[inline] pub const fn y (y: U, item: A) -> Self { Self::Y(y, item) } - #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } - } - //#[cfg(feature = "dsl")] take!($Enum, U, A|state, words|Ok( - //if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = words.peek() { - //let mut base = words.clone(); - //Some(match words.next() { - //Some(Token { value: Value::Key($x), .. }) => Self::x( - //state.give_or_fail(words, ||"x: no unit")?, - //state.give_or_fail(words, ||"x: no content")?, - //), - //Some(Token { value: Value::Key($y), .. }) => Self::y( - //state.give_or_fail(words, ||"y: no unit")?, - //state.give_or_fail(words, ||"y: no content")?, - //), - //Some(Token { value: Value::Key($x), .. }) => Self::xy( - //state.give_or_fail(words, ||"xy: no unit x")?, - //state.give_or_fail(words, ||"xy: no unit y")?, - //state.give_or_fail(words, ||"xy: no content")? - //), - //_ => unreachable!(), - //}) - //} else { - //None - //})); - impl> Content for $Enum { - fn layout (&$self, $to: E::Area) -> E::Area { - $layout.into() - } - fn content (&self) -> impl Render + '_ { - use $Enum::*; - Some(match self { X(_, c) => c, Y(_, c) => c, XY(_, _, c) => c, }) - } - } - impl $Enum { - #[inline] pub fn dx (&self) -> U { - use $Enum::*; - match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } - } - #[inline] pub fn dy (&self) -> U { - use $Enum::*; - match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } - } - } - } -} - -transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ - let [x0, y0, wmax, hmax] = to.xywh(); - let [x, y, w, h] = self.content().layout(to).xywh(); - match self { - X(_) => [x0, y, wmax, h], - Y(_) => [x, y0, w, hmax], - XY(_) => [x0, y0, wmax, hmax], - }.into() -}); - -transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ - let [x, y, w, h] = area.xywh(); - let fixed_area = match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }; - let [x, y, w, h] = Render::layout(&self.content(), fixed_area.into()).xywh(); - let fixed_area = match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }; - fixed_area -}); - -transform_xy_unit!("min/x" "min/y" "min/xy"|self: Min, area|{ - let area = Render::layout(&self.content(), area); - match self { - Self::X(mw, _) => [area.x(), area.y(), area.w().max(*mw), area.h()], - Self::Y(mh, _) => [area.x(), area.y(), area.w(), area.h().max(*mh)], - Self::XY(mw, mh, _) => [area.x(), area.y(), area.w().max(*mw), area.h().max(*mh)], - } -}); - -transform_xy_unit!("max/x" "max/y" "max/xy"|self: Max, area|{ - let [x, y, w, h] = area.xywh(); - Render::layout(&self.content(), match self { - Self::X(fw, _) => [x, y, *fw, h], - Self::Y(fh, _) => [x, y, w, *fh], - Self::XY(fw, fh, _) => [x, y, *fw, *fh], - }.into()) -}); - -transform_xy_unit!("shrink/x" "shrink/y" "shrink/xy"|self: Shrink, area|Render::layout( - &self.content(), - [area.x(), area.y(), area.w().minus(self.dx()), area.h().minus(self.dy())].into())); - -transform_xy_unit!("expand/x" "expand/y" "expand/xy"|self: Expand, area|Render::layout( - &self.content(), - [area.x(), area.y(), area.w().plus(self.dx()), area.h().plus(self.dy())].into())); - -transform_xy_unit!("push/x" "push/y" "push/xy"|self: Push, area|{ - let area = Render::layout(&self.content(), area); - [area.x().plus(self.dx()), area.y().plus(self.dy()), area.w(), area.h()] -}); - -transform_xy_unit!("pull/x" "pull/y" "pull/xy"|self: Pull, area|{ - let area = Render::layout(&self.content(), area); - [area.x().minus(self.dx()), area.y().minus(self.dy()), area.w(), area.h()] -}); - -transform_xy_unit!("margin/x" "margin/y" "margin/xy"|self: Margin, area|{ - let area = Render::layout(&self.content(), area); - let dx = self.dx(); - let dy = self.dy(); - [area.x().minus(dx), area.y().minus(dy), area.w().plus(dy.plus(dy)), area.h().plus(dy.plus(dy))] -}); - -transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ - let area = Render::layout(&self.content(), area); - let dx = self.dx(); - let dy = self.dy(); - [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] -}); diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 078e794..06b184e 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -84,7 +84,7 @@ impl ToTokens for CommandDef { let mut out = TokenStream2::new(); for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { - #arg: Dsl::try_provide(self, words)?, + #arg: DslFrom::dsl_from(self, words, ||"command error")?, }); } out @@ -93,7 +93,7 @@ impl ToTokens for CommandDef { out }; write_quote(quote! { - Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { + Some(::tengri::dsl::Token { value: ::tengri::dsl::DslVal::Key(#key), .. }) => { let mut words = words.clone(); Some(#command_enum::#variant) }, @@ -149,10 +149,12 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::Dsl<#state> for #command_enum { - fn try_provide (state: &#state, mut words: ::tengri::dsl::Ast) -> Perhaps { - let mut words = words.clone(); - let token = words.next(); + impl ::tengri::dsl::DslFrom<#state> for #command_enum { + fn try_dsl_from ( + state: &#state, + value: &impl ::tengri::dsl::Dsl + ) -> Perhaps { + use ::tengri::dsl::DslVal::*; todo!()//Ok(match token { #(#matchers)* _ => None }) } } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 2b64d1a..98b0df0 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -63,8 +63,8 @@ impl ToTokens for ExposeImpl { let formatted_type = format!("{}", quote! { #t }); let predefined = match formatted_type.as_str() { "bool" => quote! { - Some(::tengri::dsl::Value::Sym(":true")) => true, - Some(::tengri::dsl::Value::Sym(":false")) => false, + Some(::tengri::dsl::DslVal::Sym(":true")) => true, + Some(::tengri::dsl::DslVal::Sym(":false")) => false, }, "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize" => { @@ -73,7 +73,7 @@ impl ToTokens for ExposeImpl { Span::call_site() ); quote! { - Some(::tengri::dsl::Value::Num(n)) => TryInto::<#t>::try_into(n) + Some(::tengri::dsl::DslVal::Num(n)) => TryInto::<#t>::try_into(*n) .unwrap_or_else(|_|panic!(#num_err)), } }, @@ -81,17 +81,13 @@ impl ToTokens for ExposeImpl { }; let values = variants.iter().map(|(key, value)|{ let key = LitStr::new(&key, Span::call_site()); - quote! { Some(::tengri::dsl::Value::Sym(#key)) => state.#value(), } + quote! { Some(::tengri::dsl::DslVal::Sym(#key)) => state.#value(), } }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::Dsl<#state> for #t { - fn try_provide (state: &#state, mut words: ::tengri::dsl::Ast) -> Perhaps { - Ok(Some(match words.next().map(|x|x.value) { - #predefined - #(#values)* - _ => return Ok(None) - })) + impl ::tengri::dsl::DslFrom<#state> for #t { + fn try_dsl_from (state: &#state, value: &impl Dsl) -> Perhaps { + Ok(Some(match value { #predefined #(#values)* _ => return Ok(None) })) } } }); diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 75876d0..436661c 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -44,15 +44,15 @@ impl ToTokens for ViewDef { // Expressions are handled by built-in functions // that operate over constants and symbols. let builtin = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { - ::tengri::dsl::Value::Exp(_, expr) => return Ok(Some( - #builtin::try_provide(state, expr, ||"failed to load builtin")?.boxed() + ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( + #builtin::dsl_from(self, expr, ||"failed to load builtin")?.boxed() )), }); // Symbols are handled by user-taked functions // that take no parameters but `&self`. let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { - ::tengri::dsl::Value::Sym(#key) => return Ok(Some( - state.#value().boxed() + ::tengri::dsl::DslVal::Sym(#key) => return Ok(Some( + self.#value().boxed() )), })); write_quote_to(out, quote! { @@ -63,15 +63,14 @@ impl ToTokens for ViewDef { /// Makes [#self_ty] able to construct the [Render]able /// which might correspond to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl<'state> ::tengri::dsl::Dsl + 'state>> for #self_ty { - fn try_provide (state: &'state #self_ty, mut words: ::tengri::dsl::Ast) -> - Perhaps + 'state>> + impl<'state> ::tengri::dsl::DslInto< + Box + 'state> + > for #self_ty { + fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) + -> Perhaps + 'state>> { - Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { - match value { #(#builtin)* #(#exposed)* _ => None } - } else { - None - }) + use ::tengri::dsl::DslVal::*; + Ok(match dsl.val() { #(#builtin)* #(#exposed)* _ => return Ok(None) }) } } }) diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 839baee..53fe7fe 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,18 +1,17 @@ use crate::*; #[test] fn test_subcommand () -> Usually<()> { - use crate::tui::TuiIn; + use crate::{dsl::*, input::*, tui::TuiIn}; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; - use crate::input::*; - use crate::dsl::*; - struct Test { - keys: InputLayers - } + + struct Test { keys: InputLayers } + handle!(TuiIn: |self: Test, input|if let Some(command) = self.keys.command(self, input) { Ok(Some(true)) } else { Ok(None) }); + #[tengri_proc::command(Test)] impl TestCommand { fn do_thing (_state: &mut Test) -> Perhaps { @@ -25,6 +24,7 @@ use crate::*; Ok(command.execute(state)?.map(|command|Self::DoSub { command })) } } + #[tengri_proc::command(Test)] impl TestSubcommand { fn do_other_thing (_state: &mut Test) -> Perhaps { @@ -34,14 +34,16 @@ use crate::*; Ok(None) } } + let mut test = Test { - keys: InputLayers::new(SourceIter::new(" + keys: InputLayers::new(Ast::from(SourceIter::new(" (@a do-thing) (@b do-thing-arg 0) (@c do-sub do-other-thing) (@d do-sub do-other-thing-arg 0) - ".into())) + "))) }; + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { kind: KeyEventKind::Press, code: KeyCode::Char('a'), diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 63312e8..99780a0 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -70,7 +70,7 @@ content!(TuiOut: |self: Example|{ let heading = format!("Example {}/{} in {:?}", index, EXAMPLES.len(), &wh); let title = Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(heading))); let code = Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", src)))); - let content = Tui::bg(Color::Rgb(10, 10, 60), View(self, TokenIter::new(src))); + let content = Tui::bg(Color::Rgb(10, 10, 60), View(self, SourceIter::new(src))); self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content))) });