From f11c27b8c9903faffce2f765c535af918abc7cdb Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 15 Apr 2025 18:12:40 +0300 Subject: [PATCH 001/178] 0.9.0: add get_value! and get_content! macros for dsl impl --- Cargo.toml | 2 +- dsl/src/lib.rs | 20 +++++++++++++++++++ output/src/ops/transform.rs | 39 +++++++------------------------------ 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae648af..a30f6c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.8.0" +version = "0.9.0" [workspace] resolver = "2" diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 27b542c..04d234a 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -67,6 +67,26 @@ pub(crate) use std::fmt::Debug; }; } +#[macro_export] macro_rules! get_value { + ($state:expr => $token:expr) => { + if let Some(value) = $state.get(&$token.value) { + value + } else { + panic!("no value corresponding to for {:?}", &$token.value); + } + } +} + +#[macro_export] macro_rules! get_content { + ($state:expr => $token:expr) => { + if let Some(content) = $state.get_content(&$token.value) { + content + } else { + panic!("no content corresponding to for {:?}", &$token.value); + } + } +} + //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. //let source = include_str!("../../tek/src/view_arranger.edn"); diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 3dd5b6e..25afd43 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -38,13 +38,8 @@ macro_rules! transform_xy { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { 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 for {:?}", &token); - }; + let token = iter.next().expect("no content specified"); + let content = get_content!(state, token);; return Some(match k { $x => Self::x(content), $y => Self::y(content), @@ -90,17 +85,8 @@ macro_rules! transform_xy_unit { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { if k == $x || k == $y { let _ = iter.next().unwrap(); - - let u = iter.next().expect("no unit specified"); - let u = state.get(&u.value).expect("no unit provided"); - - let c = iter.next().expect("no content specified"); - let c = if let Some(c) = state.get_content(&c.value) { - c - } else { - panic!("no content corresponding to {:?}", &c); - }; - + let u = get_value!(state => iter.next().expect("no unit specified")); + let c = get_content!(state => iter.next().expect("no content specified")); return Some(match k { $x => Self::x(u, c), $y => Self::y(u, c), @@ -108,20 +94,9 @@ macro_rules! transform_xy_unit { }) } else if k == $xy { let _ = iter.next().unwrap(); - - let u = iter.next().expect("no unit specified"); - let u = state.get(&u.value).expect("no x unit provided"); - - let v = iter.next().expect("no unit specified"); - let v = state.get(&v.value).expect("no y unit provided"); - - let c = iter.next().expect("no content specified"); - let c = if let Some(c) = state.get_content(&c.value) { - c - } else { - panic!("no content corresponding to {:?}", &c); - }; - + let u = get_value!(state, iter.next().expect("no unit specified")); + let v = get_value!(state, iter.next().expect("no unit specified")); + let c = get_content!(state, iter.next().expect("no content specified")); return Some(Self::xy(u, v, c)) } } From 6048d24880324a6b411b6a8723ad95546e2edbe3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 15 Apr 2025 18:13:30 +0300 Subject: [PATCH 002/178] 0.9.1: fix get_value/get_content invocations --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- dsl/src/lib.rs | 4 ++-- output/src/ops/transform.rs | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a2cf65..0ce83b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,7 +934,7 @@ dependencies = [ [[package]] name = "tengri" -version = "0.8.0" +version = "0.9.1" dependencies = [ "tengri_dsl", "tengri_input", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "tengri_dsl" -version = "0.8.0" +version = "0.9.1" dependencies = [ "itertools 0.14.0", "konst", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "tengri_input" -version = "0.8.0" +version = "0.9.1" dependencies = [ "tengri_dsl", "tengri_tui", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "tengri_output" -version = "0.8.0" +version = "0.9.1" dependencies = [ "proptest", "proptest-derive", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "tengri_tui" -version = "0.8.0" +version = "0.9.1" dependencies = [ "atomic_float", "better-panic", diff --git a/Cargo.toml b/Cargo.toml index a30f6c6..880a3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.9.0" +version = "0.9.1" [workspace] resolver = "2" diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 04d234a..0220108 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -72,7 +72,7 @@ pub(crate) use std::fmt::Debug; if let Some(value) = $state.get(&$token.value) { value } else { - panic!("no value corresponding to for {:?}", &$token.value); + panic!("no value corresponding to {:?}", &$token.value); } } } @@ -82,7 +82,7 @@ pub(crate) use std::fmt::Debug; if let Some(content) = $state.get_content(&$token.value) { content } else { - panic!("no content corresponding to for {:?}", &$token.value); + panic!("no content corresponding to {:?}", &$token.value); } } } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 25afd43..05906d8 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -39,7 +39,7 @@ macro_rules! transform_xy { if k == $x || k == $y || k == $xy { let _ = iter.next().unwrap(); let token = iter.next().expect("no content specified"); - let content = get_content!(state, token);; + let content = get_content!(state => token);; return Some(match k { $x => Self::x(content), $y => Self::y(content), @@ -94,9 +94,9 @@ macro_rules! transform_xy_unit { }) } else if k == $xy { let _ = iter.next().unwrap(); - let u = get_value!(state, iter.next().expect("no unit specified")); - let v = get_value!(state, iter.next().expect("no unit specified")); - let c = get_content!(state, iter.next().expect("no content specified")); + let u = get_value!(state => iter.next().expect("no unit specified")); + let v = get_value!(state => iter.next().expect("no unit specified")); + let c = get_content!(state => iter.next().expect("no content specified")); return Some(Self::xy(u, v, c)) } } From 471959d1f545e95e34755f43ce192799336f4212 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 17 Apr 2025 02:21:00 +0300 Subject: [PATCH 003/178] 0.10.0: fixed expose! syntax --- Cargo.toml | 2 +- dsl/src/lib.rs | 18 +++++++++--------- output/src/ops/transform.rs | 6 ++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 880a3e6..78f56fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.9.1" +version = "0.10.0" [workspace] resolver = "2" diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 0220108..b42f442 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -38,25 +38,25 @@ pub(crate) use std::fmt::Debug; } #[macro_export] macro_rules! expose { - ($([$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { - $(expose!(@impl [$self: $State] { $($Type => { $($pat => $expr),* })* });)* + ($([$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { + $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* }; - (@impl [$self:ident:$State:ty] { $($Type:ty => { $($pat:pat => $expr:expr),* $(,)? })* }) => { - $(expose!(@type $Type [$self: $State] => { $($pat => $expr),* });)* + (@impl [$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* }) => { + $(expose!(@type [$($Type)*] [$self: $State] => { $($pat => $expr),* });)* }; - (@type bool [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + (@type [bool] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { provide_bool!(bool: |$self: $State| { $($pat => $expr),* }); }; - (@type isize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + (@type [u16] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { provide_num!(u16: |$self: $State| { $($pat => $expr),* }); }; - (@type usize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + (@type [usize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { provide_num!(usize: |$self: $State| { $($pat => $expr),* }); }; - (@type isize [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + (@type [isize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { provide_num!(isize: |$self: $State| { $($pat => $expr),* }); }; - (@type $Type:ty [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + (@type [$Type:ty] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { provide!($Type: |$self: $State| { $($pat => $expr),* }); }; } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 05906d8..15b7354 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -85,8 +85,10 @@ macro_rules! transform_xy_unit { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { if k == $x || k == $y { let _ = iter.next().unwrap(); - let u = get_value!(state => iter.next().expect("no unit specified")); - let c = get_content!(state => iter.next().expect("no content specified")); + let u = iter.next().expect("no unit specified"); + let u = get_value!(state => u); + let c = iter.next().expect("no content specified"); + let c = get_content!(state => c); return Some(match k { $x => Self::x(u, c), $y => Self::y(u, c), From aeddf561b16ad6c933a46002f5da928a05e13ab4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 18 Apr 2025 13:45:49 +0300 Subject: [PATCH 004/178] fix warnings --- output/src/ops.rs | 2 -- output/src/ops/transform.rs | 2 +- tui/src/lib.rs | 5 ----- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/output/src/ops.rs b/output/src/ops.rs index b10d8e2..b1a5786 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -1,5 +1,3 @@ -use crate::*; - mod align; pub use self::align::*; mod bsp; pub use self::bsp::*; mod cond; pub use self::cond::*; diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 15b7354..76e709d 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -39,7 +39,7 @@ macro_rules! transform_xy { if k == $x || k == $y || k == $xy { let _ = iter.next().unwrap(); let token = iter.next().expect("no content specified"); - let content = get_content!(state => token);; + let content = get_content!(state => token); return Some(match k { $x => Self::x(content), $y => Self::y(content), diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 6ac4926..f9b1b98 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -7,11 +7,6 @@ pub(crate) use ::tengri_input::*; pub use ::tengri_output as output; pub(crate) use ::tengri_output::*; -#[cfg(feature = "dsl")] -pub use ::tengri_dsl; -#[cfg(feature = "dsl")] -pub(crate) use ::tengri_dsl::*; - pub(crate) use atomic_float::AtomicF64; pub use ::better_panic; pub(crate) use ::better_panic::{Settings, Verbosity}; From 1f62c2f86dd839a3758958c53127ea892b2e79fe Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 19 Apr 2025 15:26:14 +0300 Subject: [PATCH 005/178] fix(ci): missing import in tests --- output/src/ops.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/output/src/ops.rs b/output/src/ops.rs index b1a5786..a46f3f3 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -6,6 +6,7 @@ mod map; pub use self::map::*; mod thunk; pub use self::thunk::*; mod transform; pub use self::transform::*; +#[cfg(test)] use crate::*; #[cfg(test)] #[test] fn test_ops () -> Usually<()> { Ok(()) } From fa6f6dab1da1b976b32532235dc539cb12a8068c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 23 Apr 2025 15:35:16 +0300 Subject: [PATCH 006/178] 0.10.0: update deps and lockfile --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ce83b0..460717c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,9 +275,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -713,7 +713,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -855,9 +855,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -934,7 +934,7 @@ dependencies = [ [[package]] name = "tengri" -version = "0.9.1" +version = "0.10.0" dependencies = [ "tengri_dsl", "tengri_input", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "tengri_dsl" -version = "0.9.1" +version = "0.10.0" dependencies = [ "itertools 0.14.0", "konst", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "tengri_input" -version = "0.9.1" +version = "0.10.0" dependencies = [ "tengri_dsl", "tengri_tui", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "tengri_output" -version = "0.9.1" +version = "0.10.0" dependencies = [ "proptest", "proptest-derive", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "tengri_tui" -version = "0.9.1" +version = "0.10.0" dependencies = [ "atomic_float", "better-panic", From 3861439c499313bc3c40eaf77678702b02804f12 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 24 Apr 2025 19:33:05 +0300 Subject: [PATCH 007/178] feat(input): add defcom! macro --- input/src/command.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/input/src/command.rs b/input/src/command.rs index 23b2464..d15f78a 100644 --- a/input/src/command.rs +++ b/input/src/command.rs @@ -1,4 +1,18 @@ use crate::*; + +#[macro_export] macro_rules! defcom { + (|$self:ident, $app:ident:$App:ty| $($Command:ident { $( + $Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr + )* $(,)? })*) => { + $(#[derive(Clone, Debug)] pub enum $Command { + $($Variant $(($($Param),+))?),* + })* + $(command!(|$self: $Command, $app: $App|match $self { + $($Command::$Variant $(($($param),+))? => $expr),* + });)* + } +} + #[macro_export] macro_rules! command { ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { impl$(<$($l),+>)? Command<$State> for $Command { @@ -8,6 +22,7 @@ use crate::*; } }; } + pub trait Command: Send + Sync + Sized { fn execute (self, state: &mut S) -> Perhaps; fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps @@ -16,6 +31,7 @@ pub trait Command: Send + Sync + Sized { Ok(self.execute(state)?.map(wrap)) } } + impl> Command for Option { fn execute (self, _: &mut S) -> Perhaps { Ok(None) From 91dfed107791cb631f016664613d4cf4c42d4d68 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 24 Apr 2025 21:03:52 +0300 Subject: [PATCH 008/178] feat(tui): add Tryptich (3-col layout) --- tui/src/lib.rs | 2 ++ tui/src/tui_content.rs | 19 +++++----- tui/src/tui_content/tui_tryptich.rs | 56 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 tui/src/tui_content/tui_tryptich.rs diff --git a/tui/src/lib.rs b/tui/src/lib.rs index f9b1b98..4a5f751 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(type_changing_struct_update)] + mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 0b15a95..0ea658c 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -21,12 +21,13 @@ impl> Content for std::sync::Arc { } } -mod tui_border; pub use self::tui_border::*; -mod tui_color; pub use self::tui_color::*; -mod tui_field; pub use self::tui_field::*; -mod tui_file; pub use self::tui_file::*; -mod tui_phat; pub use self::tui_phat::*; -mod tui_repeat; pub use self::tui_repeat::*; -mod tui_scroll; pub use self::tui_scroll::*; -mod tui_string; pub use self::tui_string::*; -mod tui_style; pub use self::tui_style::*; +mod tui_border; pub use self::tui_border::*; +mod tui_color; pub use self::tui_color::*; +mod tui_field; pub use self::tui_field::*; +mod tui_file; pub use self::tui_file::*; +mod tui_phat; pub use self::tui_phat::*; +mod tui_repeat; pub use self::tui_repeat::*; +mod tui_scroll; pub use self::tui_scroll::*; +mod tui_string; pub use self::tui_string::*; +mod tui_style; pub use self::tui_style::*; +mod tui_tryptich; pub use self::tui_tryptich::*; diff --git a/tui/src/tui_content/tui_tryptich.rs b/tui/src/tui_content/tui_tryptich.rs new file mode 100644 index 0000000..ef8bf6b --- /dev/null +++ b/tui/src/tui_content/tui_tryptich.rs @@ -0,0 +1,56 @@ +use crate::*; + +/// A three-column layout. +pub struct Tryptich { + pub top: bool, + pub h: u16, + pub left: (u16, A), + pub middle: (u16, B), + pub right: (u16, C), +} + +impl Tryptich<(), (), ()> { + pub fn center (h: u16) -> Self { + Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) } + } + pub fn top (h: u16) -> Self { + Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) } + } +} + +impl Tryptich { + pub fn left (self, w: u16, content: D) -> Tryptich { + Tryptich { left: (w, content), ..self } + } + pub fn middle (self, w: u16, content: D) -> Tryptich { + Tryptich { middle: (w, content), ..self } + } + pub fn right (self, w: u16, content: D) -> Tryptich { + Tryptich { right: (w, content), ..self } + } +} + +impl Content for Tryptich +where A: Content, B: Content, C: Content { + fn content (&self) -> impl Render { + let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self; + Fixed::y(h, if top { + Bsp::a( + Fill::x(Align::n(Fixed::x(w_b, Align::x(Tui::bg(Color::Reset, b))))), + Bsp::a( + Fill::x(Align::nw(Fixed::x(w_a, Tui::bg(Color::Reset, a)))), + Fill::x(Align::ne(Fixed::x(w_c, Tui::bg(Color::Reset, c)))), + ), + ) + } else { + Bsp::a( + Fill::xy(Align::c(Fixed::x(w_b, Align::x(Tui::bg(Color::Reset, b))))), + Bsp::a( + Fill::xy(Align::w(Fixed::x(w_a, Tui::bg(Color::Reset, a)))), + Fill::xy(Align::e(Fixed::x(w_c, Tui::bg(Color::Reset, c)))), + ), + ) + }) + } +} + From b5fbd14f912803fbee59bf70e08c5f3172bcc4d0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 13:58:19 +0300 Subject: [PATCH 009/178] tui_phat: export LO and HI chars --- tui/src/tui_content/tui_phat.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tui/src/tui_content/tui_phat.rs b/tui/src/tui_content/tui_phat.rs index 73f4ec1..01a852f 100644 --- a/tui/src/tui_content/tui_phat.rs +++ b/tui/src/tui_content/tui_phat.rs @@ -9,13 +9,15 @@ pub struct Phat { pub colors: [Color;4], } impl Phat { + pub const LO: &'static str = "▄"; + pub const HI: &'static str = "▀"; /// A phat line pub fn lo (fg: Color, bg: Color) -> impl Content { - Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"▄"))) + Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::LO))) } /// A phat line pub fn hi (fg: Color, bg: Color) -> impl Content { - Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(&"▀"))) + Fixed::y(1, Tui::fg_bg(fg, bg, RepeatH(Self::HI))) } } impl> Content for Phat { @@ -24,6 +26,10 @@ impl> Content for Phat { let top = Fixed::y(1, Self::lo(bg, hi)); let low = Fixed::y(1, Self::hi(bg, lo)); let content = Tui::fg_bg(fg, bg, &self.content); - Min::xy(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::xy(content)))) + Min::xy( + self.width, + self.height, + Bsp::s(top, Bsp::n(low, Fill::xy(content))) + ) } } From 649a89443bd41b35ed9d0d6acc2614bdb56c26da Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 13:58:44 +0300 Subject: [PATCH 010/178] output: finally Map::east, Map::south --- output/src/lib.rs | 1 + output/src/ops/map.rs | 79 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/output/src/lib.rs b/output/src/lib.rs index baaf657..41f824a 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -1,6 +1,7 @@ #![feature(step_trait)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] +//#![feature(impl_trait_in_fn_trait_return)] mod space; pub use self::space::*; mod ops; pub use self::ops::*; diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs index 32202bf..0f75c82 100644 --- a/output/src/ops/map.rs +++ b/output/src/ops/map.rs @@ -29,26 +29,87 @@ where I: Iterator + Send + Sync, F: Fn() -> I + Send + Sync, { - __: PhantomData<(E, B)>, + __: PhantomData<(E, B)>, /// Function that returns iterator over stacked components - get_iterator: F, + get_iter: F, /// Function that returns each stacked component - get_item: G, + get_item: G, } impl<'a, E, A, B, I, F, G> Map where I: Iterator + Send + Sync + 'a, F: Fn() -> I + Send + Sync + 'a, { - pub const fn new (get_iterator: F, get_item: G) -> Self { + pub const fn new (get_iter: F, get_item: G) -> Self { Self { __: PhantomData, - get_iterator, + get_iter, get_item } } } +impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> +where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a +{ + pub const fn east ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>>, + I, F, + impl Fn(A, usize)->Push>>> + Send + Sync + > { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for i in 0..index { + push = push + size; + } + Push::x(push, Align::w(Fixed::x(size, Fill::y(get_item(item, index))))) + } + } + } + + pub const fn south ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a + { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for i in 0..index { + push = push + size; + } + Push::y(push, Fixed::y(size, Fill::x(get_item(item, index)))) + } + } + } +} + impl<'a, E, A, B, I, F, G> Content for Map where E: Output, B: Render, @@ -57,11 +118,11 @@ impl<'a, E, A, B, I, F, G> Content for Map where G: Fn(A, usize)->B + Send + Sync { fn layout (&self, area: E::Area) -> E::Area { - let Self { get_iterator, get_item, .. } = self; + let Self { get_iter, get_item, .. } = self; let mut index = 0; let [mut min_x, mut min_y] = area.center(); let [mut max_x, mut max_y] = area.center(); - for item in get_iterator() { + for item in get_iter() { let [x,y,w,h] = get_item(item, index).layout(area).xywh(); min_x = min_x.min(x.into()); min_y = min_y.min(y.into()); @@ -75,10 +136,10 @@ impl<'a, E, A, B, I, F, G> Content for Map where area.center_xy([w.into(), h.into()].into()).into() } fn render (&self, to: &mut E) { - let Self { get_iterator, get_item, .. } = self; + let Self { get_iter, get_item, .. } = self; let mut index = 0; let area = Content::layout(self, to.area()); - for item in get_iterator() { + for item in get_iter() { let item = get_item(item, index); //to.place(area.into(), &item); to.place(item.layout(area), &item); From 2e9ba1b92d6870ccf9fe9f27a079a54b710121d0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 14:11:04 +0300 Subject: [PATCH 011/178] fix tui example --- tui/examples/tui.rs | 64 ++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index a52fbb4..f737fb3 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -2,33 +2,16 @@ use tengri::{self, input::*, output::*, tui::*, dsl::*}; use std::sync::{Arc, RwLock}; use crossterm::event::{*, KeyCode::*}; use crate::ratatui::style::Color; + fn main () -> Usually<()> { let state = Arc::new(RwLock::new(Example(10, Measure::new()))); Tui::new().unwrap().run(&state)?; Ok(()) } + #[derive(Debug)] pub struct Example(usize, Measure); -const KEYMAP: &str = "(:left prev) (:right next)"; -handle!(TuiIn: |self: Example, input|{ - let keymap = SourceIter::new(KEYMAP); - let command = keymap.command::<_, ExampleCommand, _>(self, input); - if let Some(command) = command { - command.execute(self)?; - return Ok(Some(true)) - } - return Ok(None) -}); -enum ExampleCommand { Next, Previous } -atom_command!(ExampleCommand: |app: Example| { - (":prev" [] Some(Self::Previous)) - (":next" [] Some(Self::Next)) -}); -command!(|self: ExampleCommand, state: Example|match self { - Self::Next => - { state.0 = (state.0 + 1) % EXAMPLES.len(); None }, - Self::Previous => - { state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 }; None }, -}); + +const KEYMAP: &str = "(@left prev) (@right next)"; const EXAMPLES: &'static [&'static str] = &[ include_str!("edn01.edn"), include_str!("edn02.edn"), @@ -44,6 +27,34 @@ const EXAMPLES: &'static [&'static str] = &[ include_str!("edn12.edn"), include_str!("edn13.edn"), ]; + +handle!(TuiIn: |self: Example, input|{ + Ok(if let Some(command) = SourceIter::new(KEYMAP).command::<_, ExampleCommand, _>(self, input) { + command.execute(self)?; + Some(true) + } else { + None + }) +}); + +defcom! { |self, state: Example| + ExampleCommand { + Next => { + state.0 = (state.0 + 1) % EXAMPLES.len(); + None + } + Prev => { + state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 }; + None + } + } +} + +atom_command!(ExampleCommand: |app: Example| { + ("prev" [] Some(Self::Prev)) + ("next" [] Some(Self::Next)) +}); + view!(TuiOut: |self: Example|{ let index = self.0 + 1; let wh = self.1.wh(); @@ -60,6 +71,11 @@ view!(TuiOut: |self: Example|{ ":world" => Tui::bg(Color::Rgb(100, 10, 10), "world").boxed(), ":hello-world" => "Hello world!".boxed() }); -provide_bool!(bool: |self: Example| {}); -provide_num!(u16: |self: Example| {}); -provide_num!(usize: |self: Example| {}); + +expose! { + [self: Example] { + [bool] => {} + [u16] => {} + [usize] => {} + } +} From 222e10239bb2b3e89483431545aa1670256d8a72 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 19:01:02 +0300 Subject: [PATCH 012/178] ops: fix Map::east/south with example --- output/src/ops/map.rs | 12 ++++++------ tui/examples/edn14.edn | 1 + tui/examples/edn15.edn | 1 + tui/examples/edn16.edn | 1 + tui/examples/edn17.edn | 1 + tui/examples/tui.rs | 12 +++++++++--- 6 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tui/examples/edn14.edn create mode 100644 tui/examples/edn15.edn create mode 100644 tui/examples/edn16.edn create mode 100644 tui/examples/edn17.edn diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs index 0f75c82..5dbc92b 100644 --- a/output/src/ops/map.rs +++ b/output/src/ops/map.rs @@ -62,9 +62,9 @@ where get_item: impl Fn(A, usize)->B + Send + Sync ) -> Map< E, A, - Push>>>, + Push>>, I, F, - impl Fn(A, usize)->Push>>> + Send + Sync + impl Fn(A, usize)->Push>> + Send + Sync > { Map { __: PhantomData, @@ -75,7 +75,7 @@ where for i in 0..index { push = push + size; } - Push::x(push, Align::w(Fixed::x(size, Fill::y(get_item(item, index))))) + Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) } } } @@ -86,9 +86,9 @@ where get_item: impl Fn(A, usize)->B + Send + Sync ) -> Map< E, A, - Push>>, + Push>>, I, F, - impl Fn(A, usize)->Push>> + Send + Sync + impl Fn(A, usize)->Push>> + Send + Sync > where E: Output, B: Render, @@ -104,7 +104,7 @@ where for i in 0..index { push = push + size; } - Push::y(push, Fixed::y(size, Fill::x(get_item(item, index)))) + Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) } } } diff --git a/tui/examples/edn14.edn b/tui/examples/edn14.edn new file mode 100644 index 0000000..7aab387 --- /dev/null +++ b/tui/examples/edn14.edn @@ -0,0 +1 @@ +:map-e diff --git a/tui/examples/edn15.edn b/tui/examples/edn15.edn new file mode 100644 index 0000000..ddd4d58 --- /dev/null +++ b/tui/examples/edn15.edn @@ -0,0 +1 @@ +(align/c :map-e) diff --git a/tui/examples/edn16.edn b/tui/examples/edn16.edn new file mode 100644 index 0000000..9c6b9ad --- /dev/null +++ b/tui/examples/edn16.edn @@ -0,0 +1 @@ +:map-s diff --git a/tui/examples/edn17.edn b/tui/examples/edn17.edn new file mode 100644 index 0000000..efd092f --- /dev/null +++ b/tui/examples/edn17.edn @@ -0,0 +1 @@ +(align/c :map-s) diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index f737fb3..7d9f9d7 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -25,7 +25,11 @@ const EXAMPLES: &'static [&'static str] = &[ include_str!("edn10.edn"), include_str!("edn11.edn"), include_str!("edn12.edn"), - include_str!("edn13.edn"), + //include_str!("edn13.edn"), + include_str!("edn14.edn"), + include_str!("edn15.edn"), + include_str!("edn16.edn"), + include_str!("edn17.edn"), ]; handle!(TuiIn: |self: Example, input|{ @@ -58,7 +62,7 @@ atom_command!(ExampleCommand: |app: Example| { view!(TuiOut: |self: Example|{ let index = self.0 + 1; let wh = self.1.wh(); - let src = EXAMPLES[self.0]; + let src = EXAMPLES.get(self.0).unwrap_or(&""); 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)))); @@ -69,7 +73,9 @@ view!(TuiOut: |self: Example|{ ":code" => Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed(), ":hello" => Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed(), ":world" => Tui::bg(Color::Rgb(100, 10, 10), "world").boxed(), - ":hello-world" => "Hello world!".boxed() + ":hello-world" => "Hello world!".boxed(), + ":map-e" => Map::east(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), + ":map-s" => Map::south(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), }); expose! { From aa7a2c72f5230b576d1ed95836042ec8519c8f0a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 21:07:50 +0300 Subject: [PATCH 013/178] chore: fix warnings --- output/src/ops/map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs index 5dbc92b..285bd20 100644 --- a/output/src/ops/map.rs +++ b/output/src/ops/map.rs @@ -72,7 +72,7 @@ where get_item: move |item: A, index: usize|{ // FIXME: multiply let mut push: E::Unit = E::Unit::from(0u16); - for i in 0..index { + for _ in 0..index { push = push + size; } Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) @@ -101,7 +101,7 @@ where get_item: move |item: A, index: usize|{ // FIXME: multiply let mut push: E::Unit = E::Unit::from(0u16); - for i in 0..index { + for _ in 0..index { push = push + size; } Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) From 844681d6add6838a173ddc2a5c3bb9d51451d2e1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 25 Apr 2025 21:08:13 +0300 Subject: [PATCH 014/178] 0.11.0: release --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 460717c..13a2227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -934,7 +934,7 @@ dependencies = [ [[package]] name = "tengri" -version = "0.10.0" +version = "0.11.0" dependencies = [ "tengri_dsl", "tengri_input", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "tengri_dsl" -version = "0.10.0" +version = "0.11.0" dependencies = [ "itertools 0.14.0", "konst", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "tengri_input" -version = "0.10.0" +version = "0.11.0" dependencies = [ "tengri_dsl", "tengri_tui", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "tengri_output" -version = "0.10.0" +version = "0.11.0" dependencies = [ "proptest", "proptest-derive", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "tengri_tui" -version = "0.10.0" +version = "0.11.0" dependencies = [ "atomic_float", "better-panic", @@ -1273,18 +1273,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 78f56fc..ce34095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.10.0" +version = "0.11.0" [workspace] resolver = "2" From 3d01f5558cad5a6f9ff557687b495a4f1020e6e7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Apr 2025 21:11:23 +0300 Subject: [PATCH 015/178] dsl: add sexpr syntaces; prefix modules --- dsl/src/{context.rs => dsl_context.rs} | 0 dsl/src/{error.rs => dsl_error.rs} | 0 dsl/src/{iter.rs => dsl_iter.rs} | 76 +++++------ dsl/src/dsl_macros.rs | 84 ++++++++++++ dsl/src/{token.rs => dsl_token.rs} | 60 +-------- dsl/src/lib.rs | 179 ++++++++++++++----------- tui/src/tui_engine/tui_input.rs | 13 ++ 7 files changed, 234 insertions(+), 178 deletions(-) rename dsl/src/{context.rs => dsl_context.rs} (100%) rename dsl/src/{error.rs => dsl_error.rs} (100%) rename dsl/src/{iter.rs => dsl_iter.rs} (69%) create mode 100644 dsl/src/dsl_macros.rs rename dsl/src/{token.rs => dsl_token.rs} (66%) diff --git a/dsl/src/context.rs b/dsl/src/dsl_context.rs similarity index 100% rename from dsl/src/context.rs rename to dsl/src/dsl_context.rs diff --git a/dsl/src/error.rs b/dsl/src/dsl_error.rs similarity index 100% rename from dsl/src/error.rs rename to dsl/src/dsl_error.rs diff --git a/dsl/src/iter.rs b/dsl/src/dsl_iter.rs similarity index 69% rename from dsl/src/iter.rs rename to dsl/src/dsl_iter.rs index 7f36289..de9fe7a 100644 --- a/dsl/src/iter.rs +++ b/dsl/src/dsl_iter.rs @@ -10,33 +10,56 @@ //! assert_eq!(view.peek(), view.0.peek()) //! ``` use crate::*; + /// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] /// [TokenIter::next] returns just the [Token] and mutates `self`, /// instead of returning an updated version of the struct as [SourceIter::next] does. #[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>(pub SourceIter<'a>); impl<'a> TokenIter<'a> { - pub const fn new (source: &'a str) -> Self { Self(SourceIter::new(source)) } - pub const fn peek (&self) -> Option> { self.0.peek() } + pub const fn new (source: &'a str) -> Self { + Self(SourceIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } } + impl<'a> Iterator for TokenIter<'a> { type Item = Token<'a>; fn next (&mut self) -> Option> { self.0.next().map(|(item, rest)|{self.0 = rest; item}) } } + /// Owns a reference to the source text. /// [SourceIter::next] emits subsequent pairs of: /// * a [Token] and /// * the source text remaining /// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct SourceIter<'a>(pub &'a str); +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceIter<'a>(pub &'a str); + const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); -impl<'a> From<&'a str> for SourceIter<'a> {fn from (source: &'a str) -> Self{Self::new(source)}} + +impl<'a> From<&'a str> for SourceIter<'a> { + fn from (source: &'a str) -> Self{ + Self::new(source) + } +} + impl<'a> SourceIter<'a> { - pub const fn new (source: &'a str) -> Self { Self(source) } - pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn next (mut self) -> Option<(Token<'a>, Self)> { Self::next_mut(&mut self) } - pub const fn peek (&self) -> Option> { peek_src(self.0) } + pub const fn new (source: &'a str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(Token<'a>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> { match self.peek() { Some(token) => Some((token, self.chomp(token.end()))), @@ -44,6 +67,7 @@ impl<'a> SourceIter<'a> { } } } + pub const fn peek_src <'a> (source: &'a str) -> Option> { let mut token: Token<'a> = Token::new(source, 0, 0, Nil); iterate!(char_indices(source) => (start, c) => token = match token.value() { @@ -93,6 +117,7 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { _ => Some(token), } } + pub const fn to_number (digits: &str) -> Result { let mut value = 0; iterate!(char_indices(digits) => (_, c) => match to_digit(c) { @@ -101,6 +126,7 @@ pub const fn to_number (digits: &str) -> Result { }); Ok(value) } + pub const fn to_digit (c: char) -> Result { Ok(match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, @@ -108,37 +134,3 @@ pub const fn to_digit (c: char) -> Result { _ => return Result::Err(Unexpected(c)) }) } -#[cfg(test)] mod test_token_iter { - use super::*; - //use proptest::prelude::*; - #[test] fn test_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - let mut iter = crate::TokenIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] const fn test_const_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] fn test_num () { - let digit = to_digit('0'); - let digit = to_digit('x'); - let number = to_number(&"123"); - let number = to_number(&"12asdf3"); - } - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::SourceIter::new(&source); - ////let _ = iter.next(); - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} -} diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs new file mode 100644 index 0000000..b40f380 --- /dev/null +++ b/dsl/src/dsl_macros.rs @@ -0,0 +1,84 @@ +use crate::*; + +/// Static iteration helper. +#[macro_export] macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +/// Implement the const iterator pattern. +#[macro_export] macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } + } +} + +#[macro_export] macro_rules! expose { + ($([$self:ident:$State:ty] $(($Type:ty $(: $(($pat:literal $expr:expr))*)?))*)*) => { + $(expose!(@impl [$self: $State] { $([$Type] => { $($($pat => $expr),*)? })* });)* + }; + ($([$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { + $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* + }; + (@impl [$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* }) => { + $(expose!(@type [$($Type)*] [$self: $State] => { $($pat => $expr),* });)* + }; + (@type [bool] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_bool!(bool: |$self: $State| { $($pat => $expr),* }); + }; + (@type [u16] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_num!(u16: |$self: $State| { $($pat => $expr),* }); + }; + (@type [usize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_num!(usize: |$self: $State| { $($pat => $expr),* }); + }; + (@type [isize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide_num!(isize: |$self: $State| { $($pat => $expr),* }); + }; + (@type [$Type:ty] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { + provide!($Type: |$self: $State| { $($pat => $expr),* }); + }; +} + +#[macro_export] macro_rules! impose { + ([$self:ident:$Struct:ty] $(($Command:ty : $(($cmd:literal $args:tt $result:expr))*))*) => { + $(atom_command!($Command: |$self: $Struct| { $(($cmd $args $result))* });)* + }; + ([$self:ident:$Struct:ty] { $($Command:ty => $variants:tt)* }) => { + $(atom_command!($Command: |$self: $Struct| $variants);)* + }; +} + +#[macro_export] macro_rules! get_value { + ($state:expr => $token:expr) => { + if let Some(value) = $state.get(&$token.value) { + value + } else { + panic!("no value corresponding to {:?}", &$token.value); + } + } +} + +#[macro_export] macro_rules! get_content { + ($state:expr => $token:expr) => { + if let Some(content) = $state.get_content(&$token.value) { + content + } else { + panic!("no content corresponding to {:?}", &$token.value); + } + } +} + diff --git a/dsl/src/token.rs b/dsl/src/dsl_token.rs similarity index 66% rename from dsl/src/token.rs rename to dsl/src/dsl_token.rs index 467aeeb..855a5e8 100644 --- a/dsl/src/token.rs +++ b/dsl/src/dsl_token.rs @@ -22,12 +22,14 @@ //!``` use crate::*; use self::Value::*; + #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'a> { pub source: &'a str, pub start: usize, pub length: usize, pub value: Value<'a>, } + #[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'a> { #[default] Nil, Err(ParseError), @@ -36,6 +38,7 @@ use self::Value::*; Key(&'a str), Exp(usize, TokenIter<'a>), } + impl<'a> Token<'a> { pub const fn new (source: &'a str, start: usize, length: usize, value: Value<'a>) -> Self { Self { source, start, length, value } @@ -110,60 +113,3 @@ impl<'a> Token<'a> { token } } -#[cfg(test)] mod test_token_prop { - use proptest::prelude::*; - proptest! { - #[test] fn test_token_prop ( - source in "\\PC*", - start in usize::MIN..usize::MAX, - length in usize::MIN..usize::MAX, - ) { - let token = crate::Token { - source: &source, - start, - length, - value: crate::Value::Nil - }; - let _ = token.slice(); - } - } -} -#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { - let source = ":f00"; - let mut token = Token { source, start: 0, length: 1, value: Sym(":") }; - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") }); - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") }); - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") }); - - let src = ""; - assert_eq!(None, SourceIter(src).next()); - - let src = " \n \r \t "; - assert_eq!(None, SourceIter(src).next()); - - let src = "7"; - assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value); - - let src = " 100 "; - assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value); - - let src = " 9a "; - assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value); - - let src = " :123foo "; - assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value); - - let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t"; - assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value); - - let src = "foo123"; - assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value); - - let src = "foo/bar"; - assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); - - Ok(()) -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index b42f442..f6e2dac 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,91 +1,18 @@ #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] -mod error; pub use self::error::*; -mod token; pub use self::token::*; -mod iter; pub use self::iter::*; -mod context; pub use self::context::*; + pub(crate) use self::Value::*; pub(crate) use self::ParseError::*; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use std::fmt::Debug; -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -#[macro_export] macro_rules! expose { - ($([$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { - $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* - }; - (@impl [$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* }) => { - $(expose!(@type [$($Type)*] [$self: $State] => { $($pat => $expr),* });)* - }; - (@type [bool] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_bool!(bool: |$self: $State| { $($pat => $expr),* }); - }; - (@type [u16] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(u16: |$self: $State| { $($pat => $expr),* }); - }; - (@type [usize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(usize: |$self: $State| { $($pat => $expr),* }); - }; - (@type [isize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(isize: |$self: $State| { $($pat => $expr),* }); - }; - (@type [$Type:ty] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide!($Type: |$self: $State| { $($pat => $expr),* }); - }; -} - -#[macro_export] macro_rules! impose { - ([$self:ident:$Struct:ty] { $($Command:ty => $variants:tt)* }) => { - $(atom_command!($Command: |$self: $Struct| $variants);)* - }; -} - -#[macro_export] macro_rules! get_value { - ($state:expr => $token:expr) => { - if let Some(value) = $state.get(&$token.value) { - value - } else { - panic!("no value corresponding to {:?}", &$token.value); - } - } -} - -#[macro_export] macro_rules! get_content { - ($state:expr => $token:expr) => { - if let Some(content) = $state.get_content(&$token.value) { - content - } else { - panic!("no content corresponding to {:?}", &$token.value); - } - } -} +mod dsl_error; pub use self::dsl_error::*; +mod dsl_token; pub use self::dsl_token::*; +mod dsl_iter; pub use self::dsl_iter::*; +mod dsl_context; pub use self::dsl_context::*; +mod dsl_macros; pub use self::dsl_macros::*; //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. @@ -111,3 +38,97 @@ pub(crate) use std::fmt::Debug; ////} //Ok(()) //} + +#[cfg(test)] mod test_token_iter { + use crate::*; + //use proptest::prelude::*; + #[test] fn test_iters () { + let mut iter = crate::SourceIter::new(&":foo :bar"); + let _ = iter.next(); + let mut iter = crate::TokenIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] const fn test_const_iters () { + let mut iter = crate::SourceIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] fn test_num () { + let digit = to_digit('0'); + let digit = to_digit('x'); + let number = to_number(&"123"); + let number = to_number(&"12asdf3"); + } + //proptest! { + //#[test] fn proptest_source_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::SourceIter::new(&source); + ////let _ = iter.next(); + //} + //#[test] fn proptest_token_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::TokenIter::new(&source); + ////let _ = iter.next(); + //} + //} +} + +#[cfg(test)] mod test_token_prop { + use proptest::prelude::*; + proptest! { + #[test] fn test_token_prop ( + source in "\\PC*", + start in usize::MIN..usize::MAX, + length in usize::MIN..usize::MAX, + ) { + let token = crate::Token { + source: &source, + start, + length, + value: crate::Value::Nil + }; + let _ = token.slice(); + } + } +} + +#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { + let source = ":f00"; + let mut token = Token { source, start: 0, length: 1, value: Sym(":") }; + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") }); + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") }); + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") }); + + let src = ""; + assert_eq!(None, SourceIter(src).next()); + + let src = " \n \r \t "; + assert_eq!(None, SourceIter(src).next()); + + let src = "7"; + assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value); + + let src = " 100 "; + assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value); + + let src = " 9a "; + assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value); + + let src = " :123foo "; + assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value); + + let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t"; + assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value); + + let src = "foo123"; + assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value); + + let src = "foo/bar"; + assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); + + Ok(()) +} diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index d09aa86..6f1c661 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -105,6 +105,7 @@ impl KeyMatcher { "down" => Down, "left" => Left, "right" => Right, + "esc" | "escape" => Esc, "enter" | "return" => Enter, "delete" | "del" => Delete, "tab" => Tab, @@ -120,6 +121,18 @@ impl KeyMatcher { "gt" => Char('>'), "openbracket" => Char('['), "closebracket" => Char(']'), + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), _ => return None, }) } From 7ba08b8be3e0f7f93306e745697ff072d66d8a0b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Apr 2025 21:19:48 +0300 Subject: [PATCH 016/178] output: autobox in view! macro --- output/src/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/output/src/view.rs b/output/src/view.rs index f0c88f9..8845f26 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -11,7 +11,7 @@ use crate::*; fn get_content_sym (&'a $self, value: &Value<'a>) -> Option> { if let Value::Sym(s) = value { match *s { - $($sym => Some($body),)* + $($sym => Some($body.boxed()),)* _ => None } } else { From 61fd07bffd3565d866ca2386dfc5a5c4c03141d0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 26 Apr 2025 21:31:06 +0300 Subject: [PATCH 017/178] dsl: need square brackets in expose --- dsl/src/dsl_macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs index b40f380..1aaa550 100644 --- a/dsl/src/dsl_macros.rs +++ b/dsl/src/dsl_macros.rs @@ -27,8 +27,8 @@ use crate::*; } #[macro_export] macro_rules! expose { - ($([$self:ident:$State:ty] $(($Type:ty $(: $(($pat:literal $expr:expr))*)?))*)*) => { - $(expose!(@impl [$self: $State] { $([$Type] => { $($($pat => $expr),*)? })* });)* + ($([$self:ident:$State:ty] $(([$($Type:tt)*] $(($pat:literal $expr:expr))*))*)*) => { + $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* }; ($([$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* From fa5ff90be6cbee2be6e05c39494e9b629b4134f9 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 01:57:10 +0300 Subject: [PATCH 018/178] input: sexpr defcom --- input/src/command.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/input/src/command.rs b/input/src/command.rs index d15f78a..9204c20 100644 --- a/input/src/command.rs +++ b/input/src/command.rs @@ -1,6 +1,16 @@ use crate::*; #[macro_export] macro_rules! defcom { + ([$self:ident, $app:ident:$App:ty] $(($Command:ident $(( + $Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr + ))*))*) => { + $(#[derive(Clone, Debug)] pub enum $Command { + $($Variant $(($($Param),+))?),* + })* + $(command!(|$self: $Command, $app: $App|match $self { + $($Command::$Variant $(($($param),+))? => $expr),* + });)* + }; (|$self:ident, $app:ident:$App:ty| $($Command:ident { $( $Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr )* $(,)? })*) => { @@ -10,7 +20,7 @@ use crate::*; $(command!(|$self: $Command, $app: $App|match $self { $($Command::$Variant $(($($param),+))? => $expr),* });)* - } + }; } #[macro_export] macro_rules! command { From aa66760b8c18385e326b9b45e788978f386a6c78 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 01:57:20 +0300 Subject: [PATCH 019/178] cleanup warnings --- dsl/src/dsl_macros.rs | 2 -- dsl/src/dsl_token.rs | 1 - dsl/src/lib.rs | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs index 1aaa550..b39a3ff 100644 --- a/dsl/src/dsl_macros.rs +++ b/dsl/src/dsl_macros.rs @@ -1,5 +1,3 @@ -use crate::*; - /// Static iteration helper. #[macro_export] macro_rules! iterate { ($expr:expr => $arg: pat => $body:expr) => { diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs index 855a5e8..8949091 100644 --- a/dsl/src/dsl_token.rs +++ b/dsl/src/dsl_token.rs @@ -21,7 +21,6 @@ //! })); //!``` use crate::*; -use self::Value::*; #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'a> { pub source: &'a str, diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index f6e2dac..176cfa6 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -12,7 +12,7 @@ mod dsl_error; pub use self::dsl_error::*; mod dsl_token; pub use self::dsl_token::*; mod dsl_iter; pub use self::dsl_iter::*; mod dsl_context; pub use self::dsl_context::*; -mod dsl_macros; pub use self::dsl_macros::*; +mod dsl_macros; //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. From 7ba37f3f0255c2c61ed3371e2c437514f27f6d6d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 02:04:18 +0300 Subject: [PATCH 020/178] tui: ItemPalette->ItemTheme --- tui/src/tui_content/tui_color.rs | 10 +++++----- tui/src/tui_content/tui_field.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tui/src/tui_content/tui_color.rs b/tui/src/tui_content/tui_color.rs index 6fd765c..3a8adf3 100644 --- a/tui/src/tui_content/tui_color.rs +++ b/tui/src/tui_content/tui_color.rs @@ -88,7 +88,7 @@ impl ItemColor { } /// A color in OKHSL and RGB with lighter and darker variants. -#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemPalette { +#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme { pub base: ItemColor, pub light: ItemColor, pub lighter: ItemColor, @@ -98,7 +98,7 @@ impl ItemColor { pub darkest: ItemColor, } -impl ItemPalette { +impl ItemTheme { pub const G: [Self;256] = { let mut builder = konst::array::ArrayBuilder::new(); while !builder.is_full() { @@ -109,7 +109,7 @@ impl ItemPalette { let dark = (index as f64 * 0.9) as u8; let darker = (index as f64 * 0.6) as u8; let darkest = (index as f64 * 0.3) as u8; - builder.push(ItemPalette { + builder.push(ItemTheme { base: ItemColor::from_rgb(Color::Rgb(index, index, index )), light: ItemColor::from_rgb(Color::Rgb(light, light, light, )), lighter: ItemColor::from_rgb(Color::Rgb(lighter, lighter, lighter, )), @@ -171,5 +171,5 @@ impl ItemPalette { } } -from!(|base: Color| ItemPalette = Self::from_tui_color(base)); -from!(|base: ItemColor|ItemPalette = Self::from_item_color(base)); +from!(|base: Color| ItemTheme = Self::from_tui_color(base)); +from!(|base: ItemColor|ItemTheme = Self::from_item_color(base)); diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index 26ae28e..7c83950 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -1,9 +1,9 @@ use crate::*; -pub struct FieldH(pub ItemPalette, pub T, pub U); +pub struct FieldH(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldH { fn content (&self) -> impl Render { - let Self(ItemPalette { darkest, dark, lighter, lightest, .. }, title, value) = self; + let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; row!( Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), Tui::fg_bg(lighter.rgb, dark.rgb, Tui::bold(true, title)), @@ -13,10 +13,10 @@ impl, U: Content> Content for FieldH { } } -pub struct FieldV(pub ItemPalette, pub T, pub U); +pub struct FieldV(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldV { fn content (&self) -> impl Render { - let Self(ItemPalette { darkest, dark, lighter, lightest, .. }, title, value) = self; + let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); let title = Tui::bg(dark.rgb, Tui::fg(lighter.rgb, Tui::bold(true, title))); From 95149b79c4b0d013dcba1eda80e1d14a01087fea Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 16:28:05 +0300 Subject: [PATCH 021/178] tui: add pgup/pgdn and extract tui_keys --- tui/src/lib.rs | 15 +++++ tui/src/tui_engine.rs | 57 ++++++++--------- tui/src/tui_engine/tui_input.rs | 104 +------------------------------- tui/src/tui_engine/tui_keys.rs | 90 +++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 130 deletions(-) create mode 100644 tui/src/tui_engine/tui_keys.rs diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 4a5f751..8e98c58 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -63,3 +63,18 @@ pub(crate) use std::ffi::OsString; //engine.run(&state)?; Ok(()) } + +#[cfg(test)] #[test] fn test_parse_key () { + use KeyModifiers as Mods; + let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); + //test(":x", + //KeyEvent::new(KeyCode::Char('x'), Mods::NONE)); + //test(":ctrl-x", + //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL)); + //test(":alt-x", + //KeyEvent::new(KeyCode::Char('x'), Mods::ALT)); + //test(":shift-x", + //KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT)); + //test(":ctrl-alt-shift-x", + //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT )); +} diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 8caa842..0969cc3 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -1,10 +1,11 @@ use crate::*; use std::time::Duration; -mod tui_buffer; pub use self::tui_buffer::*; -mod tui_input; pub use self::tui_input::*; -mod tui_output; pub use self::tui_output::*; -mod tui_perf; pub use self::tui_perf::*; +mod tui_buffer; pub use self::tui_buffer::*; +mod tui_input; pub use self::tui_input::*; +mod tui_keys; pub use self::tui_keys::*; +mod tui_output; pub use self::tui_output::*; +mod tui_perf; pub use self::tui_perf::*; pub struct Tui { pub exited: Arc, @@ -14,30 +15,6 @@ pub struct Tui { pub perf: PerfModel, } -pub trait TuiRun + Handle + 'static> { - /// Run an app in the main loop. - fn run (&self, state: &Arc>) -> Usually<()>; -} - -impl + Handle + 'static> TuiRun for Arc> { - fn run (&self, state: &Arc>) -> Usually<()> { - let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); - self.write().unwrap().setup()?; - let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10)); - match render_thread.join() { - Ok(result) => { - self.write().unwrap().teardown()?; - println!("\n\rRan successfully: {result:?}\n\r"); - }, - Err(error) => { - self.write().unwrap().teardown()?; - panic!("\n\rRender thread failed: {error:?}.\n\r") - }, - } - Ok(()) - } -} - impl Tui { /// Construct a new TUI engine and wrap it for shared ownership. pub fn new () -> Usually>> { @@ -89,3 +66,27 @@ impl Tui { disable_raw_mode().map_err(Into::into) } } + +pub trait TuiRun + Handle + 'static> { + /// Run an app in the main loop. + fn run (&self, state: &Arc>) -> Usually<()>; +} + +impl + Handle + 'static> TuiRun for Arc> { + fn run (&self, state: &Arc>) -> Usually<()> { + let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); + self.write().unwrap().setup()?; + let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10)); + match render_thread.join() { + Ok(result) => { + self.write().unwrap().teardown()?; + println!("\n\rRan successfully: {result:?}\n\r"); + }, + Err(error) => { + self.write().unwrap().teardown()?; + panic!("\n\rRender thread failed: {error:?}.\n\r") + }, + } + Ok(()) + } +} diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 6f1c661..3cd6373 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -11,6 +11,7 @@ impl Input for TuiIn { fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.0.store(true, Relaxed); } } + impl TuiIn { /// Spawn the input thread. pub fn run_input + 'static> ( @@ -47,6 +48,7 @@ impl TuiIn { }) } } + impl AtomInput for TuiIn { fn matches_atom (&self, token: &str) -> bool { if let Some(event) = KeyMatcher::new(token).build() { @@ -55,106 +57,4 @@ impl AtomInput for TuiIn { false } } - //fn get_event (item: &AtomItem>) -> Option { - //match item { AtomItem::Sym(s) => KeyMatcher::new(s).build(), _ => None } - //} -} -struct KeyMatcher { - valid: bool, - key: Option, - mods: KeyModifiers, -} -impl KeyMatcher { - fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "openbracket" => Char('['), - "closebracket" => Char(']'), - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } - fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } -} -#[cfg(test)] #[test] fn test_parse_key () { - use KeyModifiers as Mods; - let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); - //test(":x", - //KeyEvent::new(KeyCode::Char('x'), Mods::NONE)); - //test(":ctrl-x", - //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL)); - //test(":alt-x", - //KeyEvent::new(KeyCode::Char('x'), Mods::ALT)); - //test(":shift-x", - //KeyEvent::new(KeyCode::Char('x'), Mods::SHIFT)); - //test(":ctrl-alt-shift-x", - //KeyEvent::new(KeyCode::Char('x'), Mods::CONTROL | Mods::ALT | Mods::SHIFT )); } diff --git a/tui/src/tui_engine/tui_keys.rs b/tui/src/tui_engine/tui_keys.rs new file mode 100644 index 0000000..6e0e0e6 --- /dev/null +++ b/tui/src/tui_engine/tui_keys.rs @@ -0,0 +1,90 @@ +use crate::*; + +pub struct KeyMatcher { + valid: bool, + key: Option, + mods: KeyModifiers, +} + +impl KeyMatcher { + pub fn new (token: impl AsRef) -> Self { + let token = token.as_ref(); + if token.len() < 2 { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else if token.chars().next() != Some('@') { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else { + Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + } + } + pub fn build (self) -> Option { + if self.valid && self.key.is_some() { + Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + } else { + None + } + } + fn next (mut self, token: &str) -> Self { + let mut tokens = token.split('-').peekable(); + while let Some(token) = tokens.next() { + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + self.mods |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + self.key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + self + } + fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "tab" => Tab, + "space" => Char(' '), + "comma" => Char(','), + "period" => Char('.'), + "plus" => Char('+'), + "minus" | "dash" => Char('-'), + "equal" | "equals" => Char('='), + "underscore" => Char('_'), + "backtick" => Char('`'), + "lt" => Char('<'), + "gt" => Char('>'), + "openbracket" => Char('['), + "closebracket" => Char(']'), + "pgup" | "pageup" => PageUp, + "pgdn" | "pagedown" => PageDown, + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), + _ => return None, + }) + } +} From 47b7f7e7f9f7436b95ba8f7278bc776f28634cfc Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Apr 2025 19:41:15 +0300 Subject: [PATCH 022/178] 0.12.0: release --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13a2227..2d5c8ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,9 +910,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -934,7 +934,7 @@ dependencies = [ [[package]] name = "tengri" -version = "0.11.0" +version = "0.12.0" dependencies = [ "tengri_dsl", "tengri_input", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "tengri_dsl" -version = "0.11.0" +version = "0.12.0" dependencies = [ "itertools 0.14.0", "konst", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "tengri_input" -version = "0.11.0" +version = "0.12.0" dependencies = [ "tengri_dsl", "tengri_tui", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "tengri_output" -version = "0.11.0" +version = "0.12.0" dependencies = [ "proptest", "proptest-derive", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "tengri_tui" -version = "0.11.0" +version = "0.12.0" dependencies = [ "atomic_float", "better-panic", diff --git a/Cargo.toml b/Cargo.toml index ce34095..71ce20f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.11.0" +version = "0.12.0" [workspace] resolver = "2" From 35ad37120554611197c1e30bbed657f310d332c3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 28 Apr 2025 04:49:00 +0300 Subject: [PATCH 023/178] input: add InputMap; dsl/output/tui: Atom->Dsl --- dsl/src/dsl_context.rs | 63 ++++--- dsl/src/lib.rs | 2 +- input/src/{input.rs => _input.rs} | 0 .../src/{event_map.rs => _input_event_map.rs} | 0 input/src/command.rs | 54 ------ input/src/input_command.rs | 21 +++ input/src/input_dsl.rs | 162 ++++++++++++++++ input/src/{handle.rs => input_handle.rs} | 0 input/src/input_macros.rs | 131 +++++++++++++ input/src/keymap.rs | 177 ------------------ input/src/lib.rs | 26 +-- output/src/ops/transform.rs | 4 +- output/src/output.rs | 1 + output/src/view.rs | 6 +- tui/src/tui_engine/tui_input.rs | 4 +- 15 files changed, 374 insertions(+), 277 deletions(-) rename input/src/{input.rs => _input.rs} (100%) rename input/src/{event_map.rs => _input_event_map.rs} (100%) delete mode 100644 input/src/command.rs create mode 100644 input/src/input_command.rs create mode 100644 input/src/input_dsl.rs rename input/src/{handle.rs => input_handle.rs} (100%) create mode 100644 input/src/input_macros.rs delete mode 100644 input/src/keymap.rs diff --git a/dsl/src/dsl_context.rs b/dsl/src/dsl_context.rs index d7adc78..34f9dcd 100644 --- a/dsl/src/dsl_context.rs +++ b/dsl/src/dsl_context.rs @@ -1,48 +1,56 @@ use crate::*; -pub trait TryFromAtom<'a, T>: Sized { - fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option { None } + +pub trait TryFromDsl<'a, T>: Sized { + fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option { + None + } fn try_from_atom (state: &'a T, value: Value<'a>) -> Option { if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) } None } } -pub trait TryIntoAtom: Sized { + +pub trait TryIntoDsl: Sized { fn try_into_atom (&self) -> Option; } + /// Map EDN tokens to parameters of a given type for a given context pub trait Context: Sized { fn get (&self, _atom: &Value) -> Option { None } - fn get_or_fail (&self, atom: &Value) -> U { - self.get(atom).expect("no value") + fn get_or_fail (&self, dsl: &Value) -> U { + self.get(dsl).expect("no value") } } + impl, U> Context for &T { - fn get (&self, atom: &Value) -> Option { - (*self).get(atom) + fn get (&self, dsl: &Value) -> Option { + (*self).get(dsl) } - fn get_or_fail (&self, atom: &Value) -> U { - (*self).get_or_fail(atom) + fn get_or_fail (&self, dsl: &Value) -> U { + (*self).get_or_fail(dsl) } } + impl, U> Context for Option { - fn get (&self, atom: &Value) -> Option { - self.as_ref().map(|s|s.get(atom)).flatten() + fn get (&self, dsl: &Value) -> Option { + self.as_ref().map(|s|s.get(dsl)).flatten() } - fn get_or_fail (&self, atom: &Value) -> U { - self.as_ref().map(|s|s.get_or_fail(atom)).expect("no provider") + fn get_or_fail (&self, dsl: &Value) -> U { + self.as_ref().map(|s|s.get_or_fail(dsl)).expect("no provider") } } + /// Implement `Context` for a context and type. #[macro_export] macro_rules! provide { // Provide a value to the EDN template ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl Context<$type> for $State { #[allow(unreachable_code)] - fn get (&$self, atom: &Value) -> Option<$type> { + fn get (&$self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { $(Sym($pat) => $expr,)* _ => return None }) + Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) } } }; @@ -50,13 +58,14 @@ impl, U> Context for Option { ($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl<$lt> Context<$lt, $type> for $State { #[allow(unreachable_code)] - fn get (&$lt $self, atom: &Value) -> Option<$type> { + fn get (&$lt $self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { $(Sym($pat) => $expr,)* _ => return None }) + Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) } } }; } + /// Implement `Context` for a context and numeric type. /// /// This enables support for numeric literals. @@ -64,22 +73,23 @@ impl, U> Context for Option { // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, atom: &Value) -> Option<$type> { + fn get (&$self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) + Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) } } }; // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl Context<$type> for $State { - fn get (&$self, atom: &Value) -> Option<$type> { + fn get (&$self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) + Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) } } }; } + /// Implement `Context` for a context and the boolean type. /// /// This enables support for boolean literals. @@ -87,14 +97,14 @@ impl, U> Context for Option { // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, atom: &Value) -> Option<$type> { + fn get (&$self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { + Some(match dsl { Num(n) => match *n { 0 => false, _ => true }, Sym(":false") | Sym(":f") => false, Sym(":true") | Sym(":t") => true, $(Sym($pat) => $expr,)* - _ => return Context::get(self, atom) + _ => return Context::get(self, dsl) }) } } @@ -102,9 +112,9 @@ impl, U> Context for Option { // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { impl Context<$type> for $State { - fn get (&$self, atom: &Value) -> Option<$type> { + fn get (&$self, dsl: &Value) -> Option<$type> { use Value::*; - Some(match atom { + Some(match dsl { Num(n) => match *n { 0 => false, _ => true }, Sym(":false") | Sym(":f") => false, Sym(":true") | Sym(":t") => true, @@ -115,6 +125,7 @@ impl, U> Context for Option { } }; } + #[cfg(test)] #[test] fn test_edn_context () { struct Test; provide_bool!(bool: |self: Test|{ diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 176cfa6..81e6aa2 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -32,7 +32,7 @@ mod dsl_macros; ////include_str!("../../tui/examples/edn01.edn"), ////include_str!("../../tui/examples/edn02.edn"), ////] { - //////let items = Atom::read_all(example)?; + //////let items = Dsl::read_all(example)?; //////panic!("{layout:?}"); //////let content = >::from(&layout); ////} diff --git a/input/src/input.rs b/input/src/_input.rs similarity index 100% rename from input/src/input.rs rename to input/src/_input.rs diff --git a/input/src/event_map.rs b/input/src/_input_event_map.rs similarity index 100% rename from input/src/event_map.rs rename to input/src/_input_event_map.rs diff --git a/input/src/command.rs b/input/src/command.rs deleted file mode 100644 index 9204c20..0000000 --- a/input/src/command.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::*; - -#[macro_export] macro_rules! defcom { - ([$self:ident, $app:ident:$App:ty] $(($Command:ident $(( - $Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr - ))*))*) => { - $(#[derive(Clone, Debug)] pub enum $Command { - $($Variant $(($($Param),+))?),* - })* - $(command!(|$self: $Command, $app: $App|match $self { - $($Command::$Variant $(($($param),+))? => $expr),* - });)* - }; - (|$self:ident, $app:ident:$App:ty| $($Command:ident { $( - $Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr - )* $(,)? })*) => { - $(#[derive(Clone, Debug)] pub enum $Command { - $($Variant $(($($Param),+))?),* - })* - $(command!(|$self: $Command, $app: $App|match $self { - $($Command::$Variant $(($($param),+))? => $expr),* - });)* - }; -} - -#[macro_export] macro_rules! command { - ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { - impl$(<$($l),+>)? Command<$State> for $Command { - fn execute ($self, $state: &mut $State) -> Perhaps { - Ok($handler) - } - } - }; -} - -pub trait Command: Send + Sync + Sized { - fn execute (self, state: &mut S) -> Perhaps; - fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps - where Self: Sized - { - Ok(self.execute(state)?.map(wrap)) - } -} - -impl> Command for Option { - fn execute (self, _: &mut S) -> Perhaps { - Ok(None) - } - fn delegate (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps - where Self: Sized - { - Ok(None) - } -} diff --git a/input/src/input_command.rs b/input/src/input_command.rs new file mode 100644 index 0000000..6051277 --- /dev/null +++ b/input/src/input_command.rs @@ -0,0 +1,21 @@ +use crate::*; + +pub trait Command: Send + Sync + Sized { + fn execute (self, state: &mut S) -> Perhaps; + fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps + where Self: Sized + { + Ok(self.execute(state)?.map(wrap)) + } +} + +impl> Command for Option { + fn execute (self, _: &mut S) -> Perhaps { + Ok(None) + } + fn delegate (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps + where Self: Sized + { + Ok(None) + } +} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs new file mode 100644 index 0000000..1e67b37 --- /dev/null +++ b/input/src/input_dsl.rs @@ -0,0 +1,162 @@ +use crate::*; + +/// A [Command] that can be constructed from a [Token]. +pub trait DslCommand<'a, C>: TryFromDsl<'a, C> + Command {} + +impl<'a, C, T: TryFromDsl<'a, C> + Command> DslCommand<'a, C> for T {} + +/// [Input] state that can be matched against a [Value]. +pub trait DslInput: Input { + fn matches_dsl (&self, token: &str) -> bool; +} + +/// A pre-configured mapping of input events to commands. +pub trait KeyMap<'a, S, C: DslCommand<'a, S>, I: DslInput> { + /// Try to find a command that matches the current input event. + fn command (&'a self, state: &'a S, input: &'a I) -> Option; +} + +/// A [SourceIter] can be a [KeyMap]. +impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for SourceIter<'a> { + fn command (&'a self, state: &'a S, input: &'a I) -> Option { + let mut iter = self.clone(); + while let Some((token, rest)) = iter.next() { + iter = rest; + match token { + Token { value: Value::Exp(0, exp_iter), .. } => { + let mut exp_iter = exp_iter.clone(); + match exp_iter.next() { + Some(Token { value: Value::Sym(binding), .. }) => { + if input.matches_dsl(binding) { + if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { + return Some(command) + } + } + }, + _ => panic!("invalid config (expected symbol)") + } + }, + _ => panic!("invalid config (expected expression)") + } + } + None + } +} + +/// A [TokenIter] can be a [KeyMap]. +impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter<'a> { + fn command (&'a self, state: &'a S, input: &'a I) -> Option { + let mut iter = self.clone(); + while let Some(next) = iter.next() { + match next { + Token { value: Value::Exp(0, exp_iter), .. } => { + let mut exp_iter = exp_iter.clone(); + match exp_iter.next() { + Some(Token { value: Value::Sym(binding), .. }) => { + if input.matches_dsl(binding) { + if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { + return Some(command) + } + } + }, + _ => panic!("invalid config (expected symbol)") + } + }, + _ => panic!("invalid config (expected expression)") + } + } + None + } +} + +/// A collection of pre-configured mappings of input events to commands, +/// which may be made available subject to given conditions. +pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput> { + layers: Vec<( + Boxbool + Send + Sync + 'a>, + Box + Send + Sync + 'a> + )>, +} + +impl<'a, S, C: DslCommand<'a, S>, I: DslInput> Default for InputMap<'a, S, C, I> { + fn default () -> Self { + Self { + layers: vec![] + } + } +} + +impl<'a, S, C: DslCommand<'a, S>, I: DslInput> InputMap<'a, S, C, I> { + pub fn new (keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a) -> Self { + Self::default().layer(keymap) + } + pub fn layer ( + mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a + ) -> Self { + self.add_layer(keymap); + self + } + pub fn add_layer ( + &mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a + ) -> &mut Self { + self.add_layer_if(|_|true, keymap); + self + } + pub fn layer_if ( + mut self, + condition: impl Fn(&S)->bool + Send + Sync + 'a, + keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a + ) -> Self { + self.add_layer_if(condition, keymap); + self + } + pub fn add_layer_if ( + &mut self, + condition: impl Fn(&S)->bool + Send + Sync + 'a, + keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a + ) -> &mut Self { + self.layers.push((Box::new(condition), Box::new(keymap))); + self + } +} + +/// An [InputMap] can be a [KeyMap]. +impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I> { + fn command (&'a self, state: &'a S, input: &'a I) -> Option { + todo!() + } +} + + //fn handle (&self, state: &mut T, input: &U) -> Perhaps { + //for layer in self.layers.iter() { + //if !(layer.0)(state) { + //continue + //} + //let command: Option = SourceIter(layer.1).command::<_, _, _>(state, input); + //if let Some(command) = command { + //if let Some(undo) = command.execute(state)? { + ////app.history.push(undo); + //} + //return Ok(Some(true)) + //} + //} + //Ok(None) + //} +//} + //fn layer (mut self, keymap: &'static str) -> Self { + //self.add_layer(keymap); + //self + //} + //fn layer_if (mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> Self { + //self.add_layer_if(condition, keymap); + //self + //} + //fn add_layer (&mut self, keymap: &'static str) -> &mut Self { + //self.layers.push((Box::new(|_|true), keymap)); + //self + //} + //fn add_layer_if (&mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> &mut Self { + //self.layers.push((Box::new(condition), keymap)); + //self + //} +//} diff --git a/input/src/handle.rs b/input/src/input_handle.rs similarity index 100% rename from input/src/handle.rs rename to input/src/input_handle.rs diff --git a/input/src/input_macros.rs b/input/src/input_macros.rs new file mode 100644 index 0000000..c3e13e9 --- /dev/null +++ b/input/src/input_macros.rs @@ -0,0 +1,131 @@ +use crate::*; + +/** Implement `Command` for given `State` and collection + * of `Variant` to `handler` mappings. */ +#[macro_export] macro_rules! defcom { + ([$self:ident, $state:ident:$State:ty] $(($Command:ident $(( + $Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr + ))*))*) => { + $(#[derive(Clone, Debug)] pub enum $Command { + $($Variant $(($($Param),+))?),* + })* + $(command!(|$self: $Command, $state: $State|match $self { + $($Command::$Variant $(($($param),+))? => $expr),* + });)* + }; + (|$self:ident, $state:ident:$State:ty| $($Command:ident { $( + $Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr + )* $(,)? })*) => { + $(#[derive(Clone, Debug)] pub enum $Command { + $($Variant $(($($Param),+))?),* + })* + $(command!(|$self: $Command, $state: $State|match $self { + $($Command::$Variant $(($($param),+))? => $expr),* + });)* + }; +} + +/** Implement `Command` for given `State` and `handler` */ +#[macro_export] macro_rules! command { + ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { + impl$(<$($l),+>)? Command<$State> for $Command { + fn execute ($self, $state: &mut $State) -> Perhaps { + Ok($handler) + } + } + }; +} + +/** Implement `DslCommand` for given `State` and `Command` */ +#[cfg(feature = "dsl")] +#[macro_export] macro_rules! atom_command { + ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( + // identifier + $key:literal [ + // named parameters + $( + // argument name + $arg:ident + // if type is not provided defaults to Dsl + $( + // type:name separator + : + // argument type + $type:ty + )? + ),* + // rest of parameters + $(, ..$rest:ident)? + ] + // bound command: + $command:expr + ))* }) => { + impl<'a, $State: $Trait> TryFromDsl<'a, $State> for $Command { + fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { + let iter = iter.clone(); + match iter.next() { + $(Some(Token { value: Value::Key($key), .. }) => { + let iter = iter.clone(); + $( + let next = iter.next(); + if next.is_none() { panic!("no argument: {}", stringify!($arg)); } + let $arg = next.unwrap(); + $(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)? + )* + $(let $rest = iter.clone();)? + return $command + },)* + _ => None + } + None + } + } + }; + ($Command:ty : |$state:ident:$State:ty| { $(( + // identifier + $key:literal [ + // named parameters + $( + // argument name + $arg:ident + // if type is not provided defaults to Dsl + $( + // type:name separator + : + // argument type + $type:ty + )? + ),* + // rest of parameters + $(, ..$rest:ident)? + ] + // bound command: + $command:expr + ))* }) => { + impl<'a> TryFromDsl<'a, $State> for $Command { + fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { + 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); + }; +} diff --git a/input/src/keymap.rs b/input/src/keymap.rs deleted file mode 100644 index 629f8ac..0000000 --- a/input/src/keymap.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::*; - -/// [Input] state that can be matched against a [Value]. -pub trait AtomInput: Input { - fn matches_atom (&self, token: &str) -> bool; -} - -#[cfg(feature = "dsl")] -pub trait KeyMap<'a> { - /// Try to find a command that matches the current input event. - fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) - -> Option; -} - -#[cfg(feature = "dsl")] -impl<'a> KeyMap<'a> for SourceIter<'a> { - fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) - -> Option - { - let mut iter = self.clone(); - while let Some((token, rest)) = iter.next() { - iter = rest; - match token { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut exp_iter = exp_iter.clone(); - match exp_iter.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_atom(binding) { - if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { - return Some(command) - } - } - }, - _ => panic!("invalid config (expected symbol)") - } - }, - _ => panic!("invalid config (expected expression)") - } - } - None - } -} - -#[cfg(feature = "dsl")] -impl<'a> KeyMap<'a> for TokenIter<'a> { - fn command , I: AtomInput> (&'a self, state: &'a S, input: &'a I) - -> Option - { - let mut iter = self.clone(); - while let Some(next) = iter.next() { - match next { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut exp_iter = exp_iter.clone(); - match exp_iter.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_atom(binding) { - if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { - return Some(command) - } - } - }, - _ => panic!("invalid config (expected symbol)") - } - }, - _ => panic!("invalid config (expected expression)") - } - } - None - } -} - -/// A [Command] that can be constructed from a [Token]. -#[cfg(feature = "dsl")] -pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command {} - -#[cfg(feature = "dsl")] -impl<'a, C, T: TryFromAtom<'a, C> + Command> AtomCommand<'a, C> for T {} - -/** Implement `AtomCommand` for given `State` and `Command` */ -#[cfg(feature = "dsl")] -#[macro_export] macro_rules! atom_command { - ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( - // identifier - $key:literal [ - // named parameters - $( - // argument name - $arg:ident - // if type is not provided defaults to Atom - $( - // type:name separator - : - // argument type - $type:ty - )? - ),* - // rest of parameters - $(, ..$rest:ident)? - ] - // bound command: - $command:expr - ))* }) => { - impl<'a, $State: $Trait> TryFromAtom<'a, $State> for $Command { - fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { - 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 { - let mut iter = iter.clone(); - match iter.next() { - $(Some(Token { value: Value::Key($key), .. }) => { - let mut iter = iter.clone(); - $( - let next = iter.next(); - if next.is_none() { panic!("no argument: {}", stringify!($arg)); } - let $arg = next.unwrap(); - $(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)? - )* - $(let $rest = iter.clone();)? - return $command - }),* - _ => None - } - } - } - }; - (@bind $state:ident =>$arg:ident ? : $type:ty) => { - let $arg: Option<$type> = Context::<$type>::get($state, $arg); - }; - (@bind $state:ident => $arg:ident : $type:ty) => { - let $arg: $type = Context::<$type>::get_or_fail($state, $arg); - }; -} - -#[cfg(all(test, feature = "dsl"))] -#[test] fn test_atom_keymap () -> Usually<()> { - let keymap = SourceIter::new(""); - Ok(()) -} diff --git a/input/src/lib.rs b/input/src/lib.rs index e27a8cb..c4c230d 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,19 +1,15 @@ #![feature(associated_type_defaults)] -mod command; pub use self::command::*; -mod handle; pub use self::handle::*; -mod keymap; pub use self::keymap::*; +pub(crate) use std::error::Error; +pub(crate) type Perhaps = Result, Box>; +#[cfg(test)] pub(crate) type Usually = Result>; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; +#[cfg(feature = "dsl")] mod input_dsl; +#[cfg(feature = "dsl")] pub use self::input_dsl::*; -/// Standard error trait. -pub(crate) use std::error::Error; - -/// Standard optional result type. -pub(crate) type Perhaps = Result, Box>; - -/// Standard result type. -#[cfg(test)] -pub(crate) type Usually = Result>; +mod input_macros; +mod input_command; pub use self::input_command::*; +mod input_handle; pub use self::input_handle::*; #[cfg(test)] #[test] fn test_stub_input () -> Usually<()> { @@ -36,3 +32,9 @@ pub(crate) type Usually = Result>; assert!(!TestInput(false).is_done()); Ok(()) } + +#[cfg(all(test, feature = "dsl"))] +#[test] fn test_dsl_keymap () -> Usually<()> { + let keymap = SourceIter::new(""); + Ok(()) +} diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 76e709d..0ed9c80 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -31,7 +31,7 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: T) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> + impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T> for $Enum> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { let mut iter = iter.clone(); @@ -78,7 +78,7 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromAtom<'a, T> + impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T> for $Enum> { fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { let mut iter = iter.clone(); diff --git a/output/src/output.rs b/output/src/output.rs index af31438..98e3659 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -1,5 +1,6 @@ use crate::*; use std::ops::Deref; + /// Render target. pub trait Output: Send + Sync + Sized { /// Unit of length diff --git a/output/src/view.rs b/output/src/view.rs index 8845f26..28e15bb 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -79,8 +79,8 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync #[cfg(feature = "dsl")] #[macro_export] macro_rules! try_delegate { - ($s:ident, $atom:expr, $T:ty) => { - if let Some(value) = <$T>::try_from_atom($s, $atom) { + ($s:ident, $dsl:expr, $T:ty) => { + if let Some(value) = <$T>::try_from_atom($s, $dsl) { return Some(value.boxed()) } } @@ -89,7 +89,7 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync #[cfg(feature = "dsl")] #[macro_export] macro_rules! try_from_expr { (<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => { - impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromAtom<$l, T> for $Struct { + impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromDsl<$l, T> for $Struct { fn try_from_expr ($state: &$l T, $iter: TokenIter<'a>) -> Option { let mut $iter = $iter.clone(); $body; diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 3cd6373..2dd06f4 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -49,8 +49,8 @@ impl TuiIn { } } -impl AtomInput for TuiIn { - fn matches_atom (&self, token: &str) -> bool { +impl DslInput for TuiIn { + fn matches_dsl (&self, token: &str) -> bool { if let Some(event) = KeyMatcher::new(token).build() { &event == self.event() } else { From 4ec51d5b694c14ccf617ec4538da04089b17ab92 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 28 Apr 2025 23:28:53 +0300 Subject: [PATCH 024/178] input, dsl: implement InputMap command matching --- input/src/input_dsl.rs | 76 ++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 1e67b37..3a79fd4 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,4 +1,5 @@ use crate::*; +use std::marker::PhantomData; /// A [Command] that can be constructed from a [Token]. pub trait DslCommand<'a, C>: TryFromDsl<'a, C> + Command {} @@ -71,59 +72,84 @@ impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. -pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput> { +pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput, M: KeyMap<'a, S, C, I> + Send + Sync> { + __: &'a PhantomData<(S, C, I)>, layers: Vec<( - Boxbool + Send + Sync + 'a>, - Box + Send + Sync + 'a> + fn(&S)->bool, + M )>, } -impl<'a, S, C: DslCommand<'a, S>, I: DslInput> Default for InputMap<'a, S, C, I> { +impl<'a, S, C, I, M> Default for InputMap<'a, S, C, I, M> +where + C: DslCommand<'a, S>, + I: DslInput, + M: KeyMap<'a, S, C, I> + Send + Sync +{ fn default () -> Self { Self { + __: &PhantomData, layers: vec![] } } } -impl<'a, S, C: DslCommand<'a, S>, I: DslInput> InputMap<'a, S, C, I> { - pub fn new (keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a) -> Self { +impl<'a, S, C, I, M> InputMap<'a, S, C, I, M> +where + C: DslCommand<'a, S>, + I: DslInput, + M: KeyMap<'a, S, C, I> + Send + Sync +{ + pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } - pub fn layer ( - mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a - ) -> Self { + pub fn layer (mut self, keymap: M) -> Self { self.add_layer(keymap); self } - pub fn add_layer ( - &mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a - ) -> &mut Self { + pub fn add_layer (&mut self, keymap: M) -> &mut Self { self.add_layer_if(|_|true, keymap); self } - pub fn layer_if ( - mut self, - condition: impl Fn(&S)->bool + Send + Sync + 'a, - keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a - ) -> Self { + pub fn layer_if (mut self, condition: fn(&S)->bool, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if ( - &mut self, - condition: impl Fn(&S)->bool + Send + Sync + 'a, - keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a - ) -> &mut Self { - self.layers.push((Box::new(condition), Box::new(keymap))); + pub fn add_layer_if (&mut self, condition: fn(&S)->bool, keymap: M) -> &mut Self { + self.layers.push((condition, keymap)); self } } +impl<'a, S, C, I, M> std::fmt::Debug for InputMap<'a, S, C, I, M> +where + C: DslCommand<'a, S>, + I: DslInput, + M: KeyMap<'a, 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()); + Ok(()) + } +} + /// An [InputMap] can be a [KeyMap]. -impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I> { +impl<'a, S, C, I, M> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I, M> +where + C: DslCommand<'a, S>, + I: DslInput, + M: KeyMap<'a, S, C, I> + Send + Sync +{ fn command (&'a self, state: &'a S, input: &'a I) -> Option { - todo!() + for (condition, keymap) in self.layers.iter() { + if !condition(state) { + continue + } + if let Some(command) = keymap.command(state, input) { + return Some(command) + } + } + None } } From 119d5c35f02178b66bf69aff1087f3348f6cf96a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 30 Apr 2025 21:49:01 +0300 Subject: [PATCH 025/178] input_dsl: expose InputMap layers; add From for TokenIter --- dsl/src/dsl_iter.rs | 11 ++++++++++- input/src/input_dsl.rs | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index de9fe7a..0a94b3c 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -14,7 +14,10 @@ use crate::*; /// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] /// [TokenIter::next] returns just the [Token] and mutates `self`, /// instead of returning an updated version of the struct as [SourceIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>(pub SourceIter<'a>); +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>( + pub SourceIter<'a> +); + impl<'a> TokenIter<'a> { pub const fn new (source: &'a str) -> Self { Self(SourceIter::new(source)) @@ -31,6 +34,12 @@ impl<'a> Iterator for TokenIter<'a> { } } +impl<'a> From> for TokenIter<'a> { + fn from (source: SourceIter<'a>) -> Self{ + Self(source) + } +} + /// Owns a reference to the source text. /// [SourceIter::next] emits subsequent pairs of: /// * a [Token] and diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 3a79fd4..bcc6067 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -74,7 +74,7 @@ impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter /// which may be made available subject to given conditions. pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput, M: KeyMap<'a, S, C, I> + Send + Sync> { __: &'a PhantomData<(S, C, I)>, - layers: Vec<( + pub layers: Vec<( fn(&S)->bool, M )>, From 44ebe17c665b3a65e7a3a0020eff290093fc7ed2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 30 Apr 2025 21:51:07 +0300 Subject: [PATCH 026/178] input_dsl: cleanup commented code --- input/src/input_dsl.rs | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index bcc6067..1fe3acf 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -152,37 +152,3 @@ where None } } - - //fn handle (&self, state: &mut T, input: &U) -> Perhaps { - //for layer in self.layers.iter() { - //if !(layer.0)(state) { - //continue - //} - //let command: Option = SourceIter(layer.1).command::<_, _, _>(state, input); - //if let Some(command) = command { - //if let Some(undo) = command.execute(state)? { - ////app.history.push(undo); - //} - //return Ok(Some(true)) - //} - //} - //Ok(None) - //} -//} - //fn layer (mut self, keymap: &'static str) -> Self { - //self.add_layer(keymap); - //self - //} - //fn layer_if (mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> Self { - //self.add_layer_if(condition, keymap); - //self - //} - //fn add_layer (&mut self, keymap: &'static str) -> &mut Self { - //self.layers.push((Box::new(|_|true), keymap)); - //self - //} - //fn add_layer_if (&mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> &mut Self { - //self.layers.push((Box::new(condition), keymap)); - //self - //} -//} From 9fb5d2d9f71b8e5c28accb4220bbdc4dbc4f2da3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 30 Apr 2025 23:48:33 +0300 Subject: [PATCH 027/178] fix(tui): add feature guard --- tui/src/tui_engine/tui_input.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 2dd06f4..0c5029f 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -49,6 +49,7 @@ impl TuiIn { } } +#[cfg(feature = "dsl")] impl DslInput for TuiIn { fn matches_dsl (&self, token: &str) -> bool { if let Some(event) = KeyMatcher::new(token).build() { From 2b208e3c497d595f7dad5a6d190dfe08f7fb9dc0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 30 Apr 2025 23:48:43 +0300 Subject: [PATCH 028/178] output: collect tests; formatting --- output/src/lib.rs | 151 ++++++++++++++++++++++++++++++++ output/src/ops.rs | 5 -- output/src/ops/align.rs | 6 +- output/src/ops/bsp.rs | 25 ------ output/src/ops/map.rs | 53 +++++------ output/src/ops/transform.rs | 76 ++++++---------- output/src/space.rs | 5 -- output/src/space/area.rs | 37 -------- output/src/space/direction.rs | 27 ++---- output/src/space/size.rs | 23 ----- tui/src/tui_engine/tui_input.rs | 9 +- 11 files changed, 219 insertions(+), 198 deletions(-) diff --git a/output/src/lib.rs b/output/src/lib.rs index 41f824a..064d8da 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -19,6 +19,8 @@ pub type Usually = Result>; /// Standard optional result type. pub type Perhaps = Result, Box>; +#[cfg(test)] use proptest_derive::Arbitrary; + #[cfg(test)] #[test] fn test_stub_output () -> Usually<()> { use crate::*; struct TestOutput([u16;4]); @@ -43,3 +45,152 @@ pub type Perhaps = Result, Box>; } Ok(()) } + +#[cfg(test)] #[test] fn test_space () { + use crate::*; + assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); +} + +#[cfg(test)] #[test] fn test_iter_map () { + struct Foo; + impl Content for Foo {} + fn make_map + Send + Sync> (data: &Vec) -> impl Content { + Map::new(||data.iter(), |foo, index|{}) + } + let data = vec![Foo, Foo, Foo]; + //let map = make_map(&data); +} + +#[cfg(test)] mod test { + use crate::*; + use proptest::{prelude::*, option::of}; + use proptest::option::of; + + proptest! { + #[test] fn proptest_direction ( + d in prop_oneof![ + Just(North), Just(South), + Just(East), Just(West), + Just(Above), Just(Below) + ], + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + ) { + let _ = d.split_fixed([x, y, w, h], a); + } + } + + proptest! { + #[test] fn proptest_size ( + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + b in u16::MIN..u16::MAX, + ) { + let size = [x, y]; + let _ = size.w(); + let _ = size.h(); + let _ = size.wh(); + let _ = size.clip_w(a); + let _ = size.clip_h(b); + let _ = size.expect_min(a, b); + let _ = size.to_area_pos(); + let _ = size.to_area_size(); + } + } + + proptest! { + #[test] fn proptest_area ( + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + b in u16::MIN..u16::MAX, + ) { + let _: [u16;4] = <[u16;4] as Area>::zero(); + let _: [u16;4] = <[u16;4] as Area>::from_position([a, b]); + let _: [u16;4] = <[u16;4] as Area>::from_size([a, b]); + let area: [u16;4] = [x, y, w, h]; + let _ = area.expect_min(a, b); + let _ = area.xy(); + let _ = area.wh(); + let _ = area.xywh(); + let _ = area.clip_h(a); + let _ = area.clip_w(b); + let _ = area.clip([a, b]); + let _ = area.set_w(a); + let _ = area.set_h(b); + let _ = area.x2(); + let _ = area.y2(); + let _ = area.lrtb(); + let _ = area.center(); + let _ = area.center_x(a); + let _ = area.center_y(b); + let _ = area.center_xy([a, b]); + let _ = area.centered(); + } + } + + macro_rules! test_op_transform { + ($fn:ident, $Op:ident) => { + proptest! { + #[test] fn $fn ( + op_x in of(u16::MIN..u16::MAX), + op_y in of(u16::MIN..u16::MAX), + content in "\\PC*", + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + ) { + if let Some(op) = match (op_x, op_y) { + (Some(x), Some(y)) => Some($Op::xy(x, y, content)), + (Some(x), None) => Some($Op::x(x, content)), + (Some(y), None) => Some($Op::y(y, content)), + _ => None + } { + assert_eq!(Content::layout(&op, [x, y, w, h]), + Render::layout(&op, [x, y, w, h])); + } + } + } + } + } + + test_op_transform!(proptest_op_fixed, Fixed); + test_op_transform!(proptest_op_min, Min); + test_op_transform!(proptest_op_max, Max); + test_op_transform!(proptest_op_push, Push); + test_op_transform!(proptest_op_pull, Pull); + test_op_transform!(proptest_op_shrink, Shrink); + test_op_transform!(proptest_op_expand, Expand); + test_op_transform!(proptest_op_margin, Margin); + test_op_transform!(proptest_op_padding, Padding); + + proptest! { + #[test] fn proptest_op_bsp ( + d in prop_oneof![ + Just(North), Just(South), + Just(East), Just(West), + Just(Above), Just(Below) + ], + a in "\\PC*", + b in "\\PC*", + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + ) { + let bsp = Bsp(d, a, b); + assert_eq!( + Content::layout(&bsp, [x, y, w, h]), + Render::layout(&bsp, [x, y, w, h]), + ); + } + } + +} diff --git a/output/src/ops.rs b/output/src/ops.rs index a46f3f3..2cd1784 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -5,8 +5,3 @@ mod map; pub use self::map::*; //mod reduce; pub use self::reduce::*; mod thunk; pub use self::thunk::*; mod transform; pub use self::transform::*; - -#[cfg(test)] use crate::*; -#[cfg(test)] #[test] fn test_ops () -> Usually<()> { - Ok(()) -} diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index e9ff5a7..35a2b68 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -27,9 +27,12 @@ //! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); //! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); //! ``` + use crate::*; -#[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } +#[derive(Debug, Copy, Clone, Default)] +pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } + pub struct Align(Alignment, A); #[cfg(feature = "dsl")] @@ -79,6 +82,7 @@ impl Align { #[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) } #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } } + impl> Content for Align { fn content (&self) -> impl Render { &self.1 diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 52e697d..5ede569 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -117,28 +117,3 @@ impl, B: Content> BspAreas for Bsp { #[macro_export] macro_rules! row { ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; } -#[cfg(test)] mod test { - use super::*; - use proptest::prelude::*; - proptest! { - #[test] fn proptest_op_bsp ( - d in prop_oneof![ - Just(North), Just(South), - Just(East), Just(West), - Just(Above), Just(Below) - ], - a in "\\PC*", - b in "\\PC*", - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - ) { - let bsp = Bsp(d, a, b); - assert_eq!( - Content::layout(&bsp, [x, y, w, h]), - Render::layout(&bsp, [x, y, w, h]), - ); - } - } -} diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs index 285bd20..d124569 100644 --- a/output/src/ops/map.rs +++ b/output/src/ops/map.rs @@ -1,27 +1,4 @@ use crate::*; -#[inline] pub fn map_south( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) -} - -#[inline] pub fn map_south_west( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) -} - -#[inline] pub fn map_east( - item_offset: O::Unit, - item_width: O::Unit, - item: impl Content -) -> impl Content { - Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) -} /// Renders items from an iterator. pub struct Map @@ -148,12 +125,26 @@ impl<'a, E, A, B, I, F, G> Content for Map where } } -#[cfg(test)] #[test] fn test_iter_map () { - struct Foo; - impl Content for Foo {} - fn make_map + Send + Sync> (data: &Vec) -> impl Content { - Map::new(||data.iter(), |foo, index|{}) - } - let data = vec![Foo, Foo, Foo]; - //let map = make_map(&data); +#[inline] pub fn map_south( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) +} + +#[inline] pub fn map_south_west( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) +} + +#[inline] pub fn map_east( + item_offset: O::Unit, + item_width: O::Unit, + item: impl Content +) -> impl Content { + Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 0ed9c80..6012a96 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -19,7 +19,9 @@ //! //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 { @@ -131,6 +133,7 @@ macro_rules! transform_xy_unit { } } } + 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(); @@ -138,7 +141,9 @@ transform_xy!("fill/x" "fill/y" "fill/xy" |self: Fill, to|{ X(_) => [x0, y, wmax, h], Y(_) => [x, y0, w, hmax], XY(_) => [x0, y0, wmax, hmax], - }.into() }); + }.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 { @@ -152,80 +157,55 @@ transform_xy_unit!("fixed/x" "fixed/y" "fixed/xy"|self: Fixed, area|{ Self::Y(fh, _) => [x, y, w, *fh], Self::XY(fw, fh, _) => [x, y, *fw, *fh], }; - fixed_area }); + 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())}); + }.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()] }); + [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()] }); + [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))] }); + [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)), ] }); - -#[cfg(test)] mod test_op_transform { - use super::*; - use proptest::prelude::*; - use proptest::option::of; - macro_rules! test_op_transform { - ($fn:ident, $Op:ident) => { - proptest! { - #[test] fn $fn ( - op_x in of(u16::MIN..u16::MAX), - op_y in of(u16::MIN..u16::MAX), - content in "\\PC*", - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - ) { - if let Some(op) = match (op_x, op_y) { - (Some(x), Some(y)) => Some($Op::xy(x, y, content)), - (Some(x), None) => Some($Op::x(x, content)), - (Some(y), None) => Some($Op::y(y, content)), - _ => None - } { - assert_eq!(Content::layout(&op, [x, y, w, h]), - Render::layout(&op, [x, y, w, h])); - } - } - } - } - } - test_op_transform!(test_op_fixed, Fixed); - test_op_transform!(test_op_min, Min); - test_op_transform!(test_op_max, Max); - test_op_transform!(test_op_push, Push); - test_op_transform!(test_op_pull, Pull); - test_op_transform!(test_op_shrink, Shrink); - test_op_transform!(test_op_expand, Expand); - test_op_transform!(test_op_margin, Margin); - test_op_transform!(test_op_padding, Padding); -} + [area.x().plus(dx), area.y().plus(dy), area.w().minus(dy.plus(dy)), area.h().minus(dy.plus(dy))] +}); diff --git a/output/src/space.rs b/output/src/space.rs index 8edcb63..056e12f 100644 --- a/output/src/space.rs +++ b/output/src/space.rs @@ -3,8 +3,3 @@ mod coordinate; pub use self::coordinate::*; mod direction; pub use self::direction::*; mod measure; pub use self::measure::*; mod size; pub use self::size::*; - -#[cfg(test)] #[test] fn test_space () { - use crate::*; - assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); -} diff --git a/output/src/space/area.rs b/output/src/space/area.rs index aefcfe8..4393790 100644 --- a/output/src/space/area.rs +++ b/output/src/space/area.rs @@ -96,40 +96,3 @@ impl Area for [N;4] { fn w (&self) -> N { self[2] } fn h (&self) -> N { self[3] } } - -#[cfg(test)] mod test_area { - use super::*; - use proptest::prelude::*; - proptest! { - #[test] fn test_area_prop ( - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - b in u16::MIN..u16::MAX, - ) { - let _: [u16;4] = <[u16;4] as Area>::zero(); - let _: [u16;4] = <[u16;4] as Area>::from_position([a, b]); - let _: [u16;4] = <[u16;4] as Area>::from_size([a, b]); - let area: [u16;4] = [x, y, w, h]; - let _ = area.expect_min(a, b); - let _ = area.xy(); - let _ = area.wh(); - let _ = area.xywh(); - let _ = area.clip_h(a); - let _ = area.clip_w(b); - let _ = area.clip([a, b]); - let _ = area.set_w(a); - let _ = area.set_h(b); - let _ = area.x2(); - let _ = area.y2(); - let _ = area.lrtb(); - let _ = area.center(); - let _ = area.center_x(a); - let _ = area.center_y(b); - let _ = area.center_xy([a, b]); - let _ = area.centered(); - } - } -} diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs index afa2139..90dc95e 100644 --- a/output/src/space/direction.rs +++ b/output/src/space/direction.rs @@ -1,9 +1,12 @@ use crate::*; -#[cfg(test)] use proptest_derive::Arbitrary; + /// A cardinal direction. #[derive(Copy, Clone, PartialEq, Debug)] #[cfg_attr(test, derive(Arbitrary))] -pub enum Direction { North, South, East, West, Above, Below } +pub enum Direction { + North, South, East, West, Above, Below +} + impl Direction { pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { let [x, y, w, h] = area.xywh(); @@ -16,23 +19,3 @@ impl Direction { } } } -#[cfg(test)] mod test { - use super::*; - use proptest::prelude::*; - proptest! { - #[test] fn proptest_direction ( - d in prop_oneof![ - Just(North), Just(South), - Just(East), Just(West), - Just(Above), Just(Below) - ], - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - ) { - let _ = d.split_fixed([x, y, w, h], a); - } - } -} diff --git a/output/src/space/size.rs b/output/src/space/size.rs index cfeeeed..f9e2d19 100644 --- a/output/src/space/size.rs +++ b/output/src/space/size.rs @@ -38,26 +38,3 @@ impl Size for [N;2] { fn x (&self) -> N { self[0] } fn y (&self) -> N { self[1] } } - -#[cfg(test)] mod test_size { - use super::*; - use proptest::prelude::*; - proptest! { - #[test] fn test_size ( - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - b in u16::MIN..u16::MAX, - ) { - let size = [x, y]; - let _ = size.w(); - let _ = size.h(); - let _ = size.wh(); - let _ = size.clip_w(a); - let _ = size.clip_h(b); - let _ = size.expect_min(a, b); - let _ = size.to_area_pos(); - let _ = size.to_area_size(); - } - } -} diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 0c5029f..75c5be2 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -3,7 +3,14 @@ use std::time::Duration; use std::thread::{spawn, JoinHandle}; use crossterm::event::{poll, read}; -#[derive(Debug, Clone)] pub struct TuiIn(pub Arc, pub Event); +#[derive(Debug, Clone)] +pub struct TuiIn( + /// Exit flag + pub Arc, + /// Input event + pub Event, +); + impl Input for TuiIn { type Event = Event; type Handled = bool; From 0d4ba4a54ef0528a0a45b58e21a1e4aa7ed5eaf9 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 2 May 2025 19:16:28 +0300 Subject: [PATCH 029/178] dsl: add Str token --- dsl/src/dsl_iter.rs | 6 +++++ dsl/src/dsl_token.rs | 6 +++++ dsl/src/lib.rs | 56 ++++++++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index 0a94b3c..bc69f04 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -86,6 +86,8 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { token.grow(), '(' => Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))), + '"' => + Token::new(source, start, 1, Str(str_range(source, start, start + 1))), ':'|'@' => Token::new(source, start, 1, Sym(str_range(source, start, start + 1))), '/'|'a'..='z' => @@ -97,6 +99,10 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { }), _ => token.error(Unexpected(c)) }, + Str(s) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, Num(n) => match c { '0'..='9' => token.grow_num(n, c), ' '|'\n'|'\r'|'\t'|')' => return Some(token), diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs index 8949091..d58a60e 100644 --- a/dsl/src/dsl_token.rs +++ b/dsl/src/dsl_token.rs @@ -35,6 +35,7 @@ use crate::*; Num(usize), Sym(&'a str), Key(&'a str), + Str(&'a str), Exp(usize, TokenIter<'a>), } @@ -80,6 +81,11 @@ impl<'a> Token<'a> { token.value = Sym(token.slice_source(self.source)); token } + pub const fn grow_str (self) -> Self { + let mut token = self.grow(); + token.value = Str(token.slice_source(self.source)); + token + } pub const fn grow_exp (self) -> Self { let mut token = self.grow(); if let Exp(depth, _) = token.value { diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 81e6aa2..ea0e477 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -14,31 +14,6 @@ mod dsl_iter; pub use self::dsl_iter::*; mod dsl_context; pub use self::dsl_context::*; mod dsl_macros; -//#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { - //// 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. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} - #[cfg(test)] mod test_token_iter { use crate::*; //use proptest::prelude::*; @@ -130,5 +105,36 @@ mod dsl_macros; let src = "foo/bar"; assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); + let src = "\"foo/bar\""; + assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); + + let src = " \"foo/bar\" "; + assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); + Ok(()) } + +//#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { + //// 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. + //let mut view = TokenIter(source); + //// The token iterator wraps a const token+source iterator. + //assert_eq!(view.0.0, source); + //let mut expr = view.peek(); + //assert_eq!(view.0.0, source); + //assert_eq!(expr, Some(Token { + //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..])) + //})); + ////panic!("{view:?}"); + ////panic!("{:#?}", expr); + ////for example in [ + ////include_str!("../../tui/examples/edn01.edn"), + ////include_str!("../../tui/examples/edn02.edn"), + ////] { + //////let items = Dsl::read_all(example)?; + //////panic!("{layout:?}"); + //////let content = >::from(&layout); + ////} + //Ok(()) +//} From 3df1938626d58ffcfddc90be4ad067304102be31 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 3 May 2025 02:13:22 +0300 Subject: [PATCH 030/178] dsl: InputLayerCond; collect macros --- dsl/src/dsl_context.rs | 98 ------------------------------------------ dsl/src/dsl_iter.rs | 6 +++ dsl/src/dsl_macros.rs | 85 ++++++++++++++++++++++++++++++++++++ dsl/src/lib.rs | 14 ++++++ input/src/input_dsl.rs | 22 ++++++---- output/src/view.rs | 6 +-- 6 files changed, 121 insertions(+), 110 deletions(-) diff --git a/dsl/src/dsl_context.rs b/dsl/src/dsl_context.rs index 34f9dcd..b6daf5f 100644 --- a/dsl/src/dsl_context.rs +++ b/dsl/src/dsl_context.rs @@ -41,101 +41,3 @@ impl, U> Context for Option { self.as_ref().map(|s|s.get_or_fail(dsl)).expect("no provider") } } - -/// Implement `Context` for a context and type. -#[macro_export] macro_rules! provide { - // Provide a value to the EDN template - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - #[allow(unreachable_code)] - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) - } - } - }; - // Provide a value more generically - ($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$lt> Context<$lt, $type> for $State { - #[allow(unreachable_code)] - fn get (&$lt $self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) - } - } - }; -} - -/// Implement `Context` for a context and numeric type. -/// -/// This enables support for numeric literals. -#[macro_export] macro_rules! provide_num { - // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. - ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) - } - } - }; - // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) - } - } - }; -} - -/// Implement `Context` for a context and the boolean type. -/// -/// This enables support for boolean literals. -#[macro_export] macro_rules! provide_bool { - // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. - ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { - Num(n) => match *n { 0 => false, _ => true }, - Sym(":false") | Sym(":f") => false, - Sym(":true") | Sym(":t") => true, - $(Sym($pat) => $expr,)* - _ => return Context::get(self, dsl) - }) - } - } - }; - // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { - Num(n) => match *n { 0 => false, _ => true }, - Sym(":false") | Sym(":f") => false, - Sym(":true") | Sym(":t") => true, - $(Sym($pat) => $expr,)* - _ => return None - }) - } - } - }; -} - -#[cfg(test)] #[test] fn test_edn_context () { - struct Test; - provide_bool!(bool: |self: Test|{ - ":provide-bool" => true - }); - let test = Test; - assert_eq!(test.get(&Value::Sym(":false")), Some(false)); - assert_eq!(test.get(&Value::Sym(":true")), Some(true)); - assert_eq!(test.get(&Value::Sym(":provide-bool")), Some(true)); - assert_eq!(test.get(&Value::Sym(":missing-bool")), None); - assert_eq!(test.get(&Value::Num(0)), Some(false)); - assert_eq!(test.get(&Value::Num(1)), Some(true)); -} diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index bc69f04..269bc55 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -34,6 +34,12 @@ impl<'a> Iterator for TokenIter<'a> { } } +impl<'a> From<&'a str> for TokenIter<'a> { + fn from (source: &'a str) -> Self{ + Self(SourceIter(source)) + } +} + impl<'a> From> for TokenIter<'a> { fn from (source: SourceIter<'a>) -> Self{ Self(source) diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs index b39a3ff..338583f 100644 --- a/dsl/src/dsl_macros.rs +++ b/dsl/src/dsl_macros.rs @@ -24,6 +24,7 @@ } } +/// Implement `Context` for one or more base structs, types, and keys. */ #[macro_export] macro_rules! expose { ($([$self:ident:$State:ty] $(([$($Type:tt)*] $(($pat:literal $expr:expr))*))*)*) => { $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* @@ -51,6 +52,90 @@ }; } +/// Implement `Context` for a context and type. +#[macro_export] macro_rules! provide { + // Provide a value to the EDN template + ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context<$type> for $State { + #[allow(unreachable_code)] + fn get (&$self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) + } + } + }; + // Provide a value more generically + ($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl<$lt> Context<$lt, $type> for $State { + #[allow(unreachable_code)] + fn get (&$lt $self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) + } + } + }; +} + +/// Implement `Context` for a context and numeric type. +/// +/// This enables support for numeric literals. +#[macro_export] macro_rules! provide_num { + // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. + ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl<$T: $Trait> Context<$type> for $T { + fn get (&$self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) + } + } + }; + // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. + ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context<$type> for $State { + fn get (&$self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) + } + } + }; +} + +/// Implement `Context` for a context and the boolean type. +/// +/// This enables support for boolean literals. +#[macro_export] macro_rules! provide_bool { + // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. + ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl<$T: $Trait> Context<$type> for $T { + fn get (&$self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { + Num(n) => match *n { 0 => false, _ => true }, + Sym(":false") | Sym(":f") => false, + Sym(":true") | Sym(":t") => true, + $(Sym($pat) => $expr,)* + _ => return Context::get(self, dsl) + }) + } + } + }; + // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. + ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { + impl Context<$type> for $State { + fn get (&$self, dsl: &Value) -> Option<$type> { + use Value::*; + Some(match dsl { + Num(n) => match *n { 0 => false, _ => true }, + Sym(":false") | Sym(":f") => false, + Sym(":true") | Sym(":t") => true, + $(Sym($pat) => $expr,)* + _ => return None + }) + } + } + }; +} + #[macro_export] macro_rules! impose { ([$self:ident:$Struct:ty] $(($Command:ty : $(($cmd:literal $args:tt $result:expr))*))*) => { $(atom_command!($Command: |$self: $Struct| { $(($cmd $args $result))* });)* diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index ea0e477..994fc80 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -114,6 +114,20 @@ mod dsl_macros; Ok(()) } +#[cfg(test)] #[test] fn test_dsl_context () { + struct Test; + provide_bool!(bool: |self: Test|{ + ":provide-bool" => true + }); + let test = Test; + assert_eq!(test.get(&Value::Sym(":false")), Some(false)); + assert_eq!(test.get(&Value::Sym(":true")), Some(true)); + assert_eq!(test.get(&Value::Sym(":provide-bool")), Some(true)); + assert_eq!(test.get(&Value::Sym(":missing-bool")), None); + assert_eq!(test.get(&Value::Num(0)), Some(false)); + assert_eq!(test.get(&Value::Num(1)), Some(true)); +} + //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. //let source = include_str!("../../tek/src/view_arranger.edn"); diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 1fe3acf..49f77fc 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -70,14 +70,18 @@ impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter } } +pub type InputLayerCond<'a, S> = Boxbool + Send + Sync + 'a>; + /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. -pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput, M: KeyMap<'a, S, C, I> + Send + Sync> { +pub struct InputMap<'a, S, C, I, M> +where + C: DslCommand<'a, S>, + I: DslInput, + M: KeyMap<'a, S, C, I> + Send + Sync +{ __: &'a PhantomData<(S, C, I)>, - pub layers: Vec<( - fn(&S)->bool, - M - )>, + pub layers: Vec<(InputLayerCond<'a, S>, M)>, } impl<'a, S, C, I, M> Default for InputMap<'a, S, C, I, M> @@ -108,15 +112,15 @@ where self } pub fn add_layer (&mut self, keymap: M) -> &mut Self { - self.add_layer_if(|_|true, keymap); + self.add_layer_if(Box::new(|_|true), keymap); self } - pub fn layer_if (mut self, condition: fn(&S)->bool, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputLayerCond<'a, S>, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if (&mut self, condition: fn(&S)->bool, keymap: M) -> &mut Self { - self.layers.push((condition, keymap)); + pub fn add_layer_if (&mut self, condition: InputLayerCond<'a, S>, keymap: M) -> &mut Self { + self.layers.push((Box::new(condition), keymap)); self } } diff --git a/output/src/view.rs b/output/src/view.rs index 28e15bb..c18713f 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -27,14 +27,14 @@ use crate::*; #[cfg(feature = "dsl")] pub struct View<'a, T>( pub &'a T, - pub SourceIter<'a> + 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 iter = self.1.clone(); - while let Some((Token { value, .. }, _)) = iter.next() { + 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) } From 21f7f6b38afc966b7b45af442935d48c8c5067d3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 3 May 2025 02:14:03 +0300 Subject: [PATCH 031/178] 0.13.0: release --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d5c8ab..96211a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,9 +304,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -928,13 +928,13 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] [[package]] name = "tengri" -version = "0.12.0" +version = "0.13.0" dependencies = [ "tengri_dsl", "tengri_input", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "tengri_dsl" -version = "0.12.0" +version = "0.13.0" dependencies = [ "itertools 0.14.0", "konst", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "tengri_input" -version = "0.12.0" +version = "0.13.0" dependencies = [ "tengri_dsl", "tengri_tui", @@ -963,7 +963,7 @@ dependencies = [ [[package]] name = "tengri_output" -version = "0.12.0" +version = "0.13.0" dependencies = [ "proptest", "proptest-derive", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "tengri_tui" -version = "0.12.0" +version = "0.13.0" dependencies = [ "atomic_float", "better-panic", diff --git a/Cargo.toml b/Cargo.toml index 71ce20f..daeb8ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.12.0" +version = "0.13.0" [workspace] resolver = "2" From 2c797fd41f114b103ce1c65c7e1143892999e753 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 3 May 2025 17:04:21 +0300 Subject: [PATCH 032/178] wip: scaffold proc crate and view macro --- Cargo.lock | 9 +++++ Cargo.toml | 4 ++- dsl/Cargo.toml | 2 +- input/Cargo.toml | 2 +- output/Cargo.toml | 2 +- proc/Cargo.toml | 13 +++++++ proc/src/lib.rs | 26 ++++++++++++++ proc/src/proc_view.rs | 80 +++++++++++++++++++++++++++++++++++++++++++ tui/Cargo.toml | 2 +- 9 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 proc/Cargo.toml create mode 100644 proc/src/lib.rs create mode 100644 proc/src/proc_view.rs diff --git a/Cargo.lock b/Cargo.lock index 96211a0..4e5ce2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,6 +972,15 @@ dependencies = [ "tengri_tui", ] +[[package]] +name = "tengri_proc" +version = "0.13.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tengri_tui" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index daeb8ce..4a6d408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace.package] version = "0.13.0" +edition = "2024" [workspace] resolver = "2" @@ -8,7 +9,8 @@ members = [ "./input", "./output", "./tui", - "./dsl" + "./dsl", + "./proc", ] [profile.release] diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 94238a8..3609ada 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_dsl" -edition = "2024" description = "UI metaframework, tiny S-expression-based DSL." version = { workspace = true } +edition = { workspace = true } [dependencies] konst = { version = "0.3.16", features = [ "rust_1_83" ] } diff --git a/input/Cargo.toml b/input/Cargo.toml index a3ca0ce..3b7772f 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_input" -edition = "2024" description = "UI metaframework, input layer." version = { workspace = true } +edition = { workspace = true } [dependencies] tengri_dsl = { optional = true, path = "../dsl" } diff --git a/output/Cargo.toml b/output/Cargo.toml index b208a5b..2505df5 100644 --- a/output/Cargo.toml +++ b/output/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_output" -edition = "2024" description = "UI metaframework, output layer." version = { workspace = true } +edition = { workspace = true } [dependencies] tengri_dsl = { optional = true, path = "../dsl" } diff --git a/proc/Cargo.toml b/proc/Cargo.toml new file mode 100644 index 0000000..2462993 --- /dev/null +++ b/proc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tengri_proc" +description = "UI metaframework, procedural macros." +version = { workspace = true } +edition = { workspace = true } + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = { version = "1" } +proc-macro2 = { version = "1", features = ["span-locations"] } diff --git a/proc/src/lib.rs b/proc/src/lib.rs new file mode 100644 index 0000000..0709e4b --- /dev/null +++ b/proc/src/lib.rs @@ -0,0 +1,26 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2}; + +mod proc_view; + +#[proc_macro_attribute] +pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { + self::proc_view::view_impl(meta.into(), item.into()).into() + //for attr in syn::parse_macro_input!(meta as syn::MetaList).iter() { + //} + //let item = syn::parse_macro_input!(item as syn::ItemImpl); + //let output = "TuiOut"; + //let target = "Tek"; + //let define = "self.config.view"; + //let expose = vec![]; + //let output = format!( + //"::tengri_dsl::view!({}:|self:{}|self.size.of(::tengri_dsl::View(self,{}));{{{}}});", + //output, + //target, + //define, + //expose.iter().fold(String::new(), |acc, (key, value)|format!("{acc},{key}=>{value}")), + //); + //let output = ""; + //output.parse().unwrap() +} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs new file mode 100644 index 0000000..b9737d1 --- /dev/null +++ b/proc/src/proc_view.rs @@ -0,0 +1,80 @@ +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2}; + +pub(crate) fn view_impl (meta: TokenStream, item: TokenStream) -> TokenStream { + let ViewMeta { output, define, attrs } = syn::parse_macro_input!(meta as ViewMeta); + let ViewItem { target, mapped, items } = syn::parse_macro_input!(item as ViewItem); + quote::quote! { + #attrs + impl #target { + #items + } + impl ::tengri::Content<#output> for #target { + fn content (&self) -> impl Render<#output> { + self.size.of(::tengri::View(self, #define)) + } + } + impl<'a> ::tengri::ViewContext<'a, #output> for #target { + fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + match value { + #mapped + _ => panic!("expected Sym(content), got: {value:?}") + } + //if let Value::Sym(s) = value { + //match *s { + //$($sym => Some($body.boxed()),)* + //_ => None + //} + //} else { + //panic!("expected Sym(content), got: {value:?}") + //} + } + } + }.into() +} + +struct ViewMeta { + attrs: &'static str, + output: &'static str, + define: &'static str, +} + +impl syn::parse::Parse for ViewMeta { + fn parse (input: syn::parse::ParseStream) -> syn::parse::Result { + Ok(Self { + attrs: "", + output: "", + define: "", + }) + } +} + +struct ViewItem { + items: &'static str, + target: &'static str, + mapped: &'static str, +} + +impl syn::parse::Parse for ViewItem { + fn parse (input: syn::parse::ParseStream) -> syn::parse::Result { + Ok(Self { + items: "", + target: "", + mapped: "", + }) + } +} + +#[cfg(test)] #[test] fn test_view () { + + let _: syn::ItemImpl = syn::parse_quote! { + #[tengri::view(Tui)] + impl SomeView { + #[tengri::view(":view")] + fn view (&self) -> impl Content + use<'_> { + "view" + } + } + }; + +} diff --git a/tui/Cargo.toml b/tui/Cargo.toml index e005d37..5bf0dc2 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "tengri_tui" -edition = "2024" description = "UI metaframework, Ratatui backend." version = { workspace = true } +edition = { workspace = true } [dependencies] palette = { version = "0.7.6", features = [ "random" ] } From b543c43e68154f049019da648064f36af1537434 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 3 May 2025 18:11:10 +0300 Subject: [PATCH 033/178] proc: view macro implementation --- proc/Cargo.toml | 2 +- proc/src/proc_view.rs | 335 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 278 insertions(+), 59 deletions(-) diff --git a/proc/Cargo.toml b/proc/Cargo.toml index 2462993..aa2cd24 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -8,6 +8,6 @@ edition = { workspace = true } proc-macro = true [dependencies] -syn = { version = "2", features = ["full"] } +syn = { version = "2", features = ["full", "extra-traits"] } quote = { version = "1" } proc-macro2 = { version = "1", features = ["span-locations"] } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index b9737d1..7095fce 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -1,80 +1,299 @@ use proc_macro::TokenStream; -use proc_macro2::{TokenStream as TokenStream2}; +use proc_macro2::{ + TokenStream as TokenStream2, TokenTree, + Ident, Span, Punct, Spacing, Group, Delimiter, Literal +}; +use syn::{parse, parse_macro_input, braced, Token}; +use syn::{Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::token::{PathSep, Brace}; +use syn::punctuated::Punctuated; +use quote::{quote, TokenStreamExt, ToTokens}; -pub(crate) fn view_impl (meta: TokenStream, item: TokenStream) -> TokenStream { - let ViewMeta { output, define, attrs } = syn::parse_macro_input!(meta as ViewMeta); - let ViewItem { target, mapped, items } = syn::parse_macro_input!(item as ViewItem); - quote::quote! { - #attrs - impl #target { - #items - } - impl ::tengri::Content<#output> for #target { - fn content (&self) -> impl Render<#output> { - self.size.of(::tengri::View(self, #define)) - } - } - impl<'a> ::tengri::ViewContext<'a, #output> for #target { - fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { - match value { - #mapped - _ => panic!("expected Sym(content), got: {value:?}") - } - //if let Value::Sym(s) = value { - //match *s { - //$($sym => Some($body.boxed()),)* - //_ => None - //} - //} else { - //panic!("expected Sym(content), got: {value:?}") - //} - } - } - }.into() +pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { + let mut out = TokenStream2::new(); + ViewDefinition { + meta: parse_macro_input!(meta as ViewMeta), + data: parse_macro_input!(data as ViewImpl), + }.to_tokens(&mut out); + out.into() } +#[derive(Debug, Clone)] +struct ViewDefinition { + meta: ViewMeta, + data: ViewImpl, +} + +#[derive(Debug, Clone)] struct ViewMeta { - attrs: &'static str, - output: &'static str, - define: &'static str, + output: Ident, + //attrs: Vec, } -impl syn::parse::Parse for ViewMeta { - fn parse (input: syn::parse::ParseStream) -> syn::parse::Result { - Ok(Self { - attrs: "", - output: "", - define: "", - }) - } +#[derive(Debug, Clone)] +struct ViewImpl { + target: Ident, + items: Vec, + syms: Vec, } +#[derive(Debug, Clone)] struct ViewItem { - items: &'static str, - target: &'static str, - mapped: &'static str, + item: ImplItem, + expose: Option, } -impl syn::parse::Parse for ViewItem { - fn parse (input: syn::parse::ParseStream) -> syn::parse::Result { +#[derive(Debug, Clone)] +struct ViewSym { + symbol: Literal, + name: Ident, +} + +impl Parse for ViewDefinition { + fn parse (input: ParseStream) -> Result { Ok(Self { - items: "", - target: "", - mapped: "", + meta: input.parse::()?, + data: input.parse::()?, }) } } -#[cfg(test)] #[test] fn test_view () { +impl Parse for ViewMeta { + fn parse (input: ParseStream) -> Result { + Ok(Self { + output: input.parse::()?, + }) + } +} - let _: syn::ItemImpl = syn::parse_quote! { - #[tengri::view(Tui)] - impl SomeView { - #[tengri::view(":view")] - fn view (&self) -> impl Content + use<'_> { - "view" +impl Parse for ViewImpl { + fn parse (input: ParseStream) -> Result { + let _ = input.parse::()?; + let mut syms = vec![]; + Ok(Self { + target: input.parse::()?, + items: { + let group; + let brace = braced!(group in input); + let mut items = vec![]; + while !group.is_empty() { + let item = group.parse::()?; + if let Some(expose) = &item.expose { + if let ImplItem::Fn(ref item) = item.item { + let symbol = expose.clone(); + let name = item.sig.ident.clone(); + syms.push(ViewSym { symbol, name }) + } else { + return Err( + input.error("only fn items can be exposed to #[tengri::view]") + ) + } + } + items.push(item); + } + items + }, + syms, + }) + } +} + + +impl Parse for ViewItem { + fn parse (input: ParseStream) -> Result { + let mut expose = None; + Ok(Self { + item: { + let mut item = input.parse::()?; + if let ImplItem::Fn(ref mut item) = item { + item.attrs = item.attrs.iter().filter(|attr| { + if let Attribute { + meta: Meta::List(MetaList { path, tokens, .. }), .. + } = attr + && path.segments.len() == 2 + && nth_segment_is(&path.segments, 0, "tengri") + && nth_segment_is(&path.segments, 1, "view") + && let Some(TokenTree::Literal(name)) = tokens.clone().into_iter().next() + { + expose = Some(name); + return false + } + true + }).map(|x|x.clone()).collect(); + }; + item + }, + expose, + }) + } +} + +impl ToTokens for ViewSym { + fn to_tokens (&self, out: &mut TokenStream2) { + use Spacing::*; + 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(self.symbol.clone()); + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + out.append(Ident::new("Some", Span::call_site())); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + out.append(Ident::new("self", Span::call_site())); + out.append(Punct::new('.', Alone)); + out.append(self.name.clone()); + out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + out.append(Punct::new('.', Alone)); + out.append(Ident::new("boxed", Span::call_site())); + out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + out + })); + out.append(Punct::new(',', Alone)); + } +} + +impl ToTokens for ViewItem { + fn to_tokens (&self, out: &mut TokenStream2) { + self.item.to_tokens(out) + } +} + +impl ToTokens for ViewDefinition { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self { + meta: ViewMeta { output }, + data: ViewImpl { target, syms, items }, + } = self; + for token in quote! { + /// Augmented by [tengri_proc]. + impl #target { + #(#items)* } + /// Generated by [tengri_proc]. + impl ::tengri::output::Content<#output> for #target { + fn content (&self) -> impl Render<#output> { + self.size.of(::tengri::output::View(self, self.config.view)) + } + } + /// Generated by [tengri_proc]. + impl<'a> ::tengri::output::ViewContext<'a, #output> for #target { + fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + match value { + #(#syms)* + _ => panic!("expected Sym(content), got: {value:?}") + } + } + } + } { + out.append(token) + } + } +} + +fn nth_segment_is (segments: &Punctuated, n: usize, x: &str) -> bool { + if let Some(PathSegment { arguments: PathArguments::None, ident, .. }) = segments.get(n) { + if format!("{ident}") == x { + return true + } + } + return false +} + +impl std::cmp::PartialEq for ViewItem { + fn eq (&self, other: &Self) -> bool { + self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) + } +} + +impl std::cmp::PartialEq for ViewSym { + fn eq (&self, other: &Self) -> bool { + self.name == other.name && (format!("{}", self.symbol) == format!("{}", other.symbol)) + } +} + +#[cfg(test)] use syn::{ItemImpl, parse_quote as pq}; + +#[cfg(test)] #[test] fn test_view_meta () { + let x: ViewMeta = pq! { SomeOutput }; + let output: Ident = pq! { SomeOutput }; + assert_eq!(x.output, output); +} + +#[cfg(test)] #[test] fn test_view_impl () { + let x: ViewImpl = pq! { + impl Foo { + /// docstring1 + #[tengri::view(":view1")] #[bar] fn a_view () {} + + #[baz] + /// docstring2 + #[baz] fn is_not_view () {} } }; - + let expected_target: Ident = pq! { Foo }; + assert_eq!(x.target, expected_target); + assert_eq!(x.items.len(), 2); + assert_eq!(x.items[0].item, pq! { + /// docstring1 + #[bar] fn a_view () {} + }); + assert_eq!(x.items[1].item, pq! { + #[baz] + /// docstring2 + #[baz] fn is_not_view () {} + }); + assert_eq!(x.syms, vec![ + ViewSym { symbol: pq! { ":view1" }, name: pq! { a_view }, }, + ]); +} + +#[cfg(test)] #[test] fn test_view_definition () { + // FIXME + //let parsed: ViewDefinition = pq! { + //#[tengri_proc::view(SomeOutput)] + //impl SomeView { + //#[tengri::view(":view-1")] + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + //}; + //let written = quote! { #parsed }; + //assert_eq!(format!("{written}"), format!("{}", quote! { + //impl SomeView { + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + ///// Generated by [tengri_proc]. + //impl ::tengri::output::Content for SomeView { + //fn content (&self) -> impl Render { + //self.size.of(::tengri::output::View(self, self.config.view)) + //} + //} + ///// Generated by [tengri_proc]. + //impl<'a> ::tengri::dsl::ViewContext<'a, SomeOutput> for SomeView { + //fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + //match value { + //::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(), + //_ => panic!("expected Sym(content), got: {value:?}") + //} + //} + //} + //})); } From cba23a005cc991e642c834947da87ff6b31638e2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 4 May 2025 19:59:41 +0300 Subject: [PATCH 034/178] proc: expose macro implementation --- proc/src/lib.rs | 41 +++-- proc/src/proc_expose.rs | 392 ++++++++++++++++++++++++++++++++++++++++ proc/src/proc_view.rs | 18 +- 3 files changed, 417 insertions(+), 34 deletions(-) create mode 100644 proc/src/proc_expose.rs diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 0709e4b..52c8814 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,26 +1,31 @@ extern crate proc_macro; -use proc_macro::TokenStream; -use proc_macro2::{TokenStream as TokenStream2}; + +pub(crate) use std::collections::{HashMap, BTreeMap}; +pub(crate) use std::cmp::Ordering; +pub(crate) use proc_macro::TokenStream; +pub(crate) use proc_macro2::{ + TokenStream as TokenStream2, TokenTree, + Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal +}; +pub(crate) use syn::{ + parse, parse_macro_input, parse_quote as pq, + braced, bracketed, parenthesized, Token, + Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr, Type, + parse::{Parse, ParseStream, Result}, + token::{PathSep, Brace}, + punctuated::Punctuated, +}; +pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; mod proc_view; +mod proc_expose; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { self::proc_view::view_impl(meta.into(), item.into()).into() - //for attr in syn::parse_macro_input!(meta as syn::MetaList).iter() { - //} - //let item = syn::parse_macro_input!(item as syn::ItemImpl); - //let output = "TuiOut"; - //let target = "Tek"; - //let define = "self.config.view"; - //let expose = vec![]; - //let output = format!( - //"::tengri_dsl::view!({}:|self:{}|self.size.of(::tengri_dsl::View(self,{}));{{{}}});", - //output, - //target, - //define, - //expose.iter().fold(String::new(), |acc, (key, value)|format!("{acc},{key}=>{value}")), - //); - //let output = ""; - //output.parse().unwrap() +} + +#[proc_macro_attribute] +pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { + self::proc_expose::expose_impl(meta.into(), item.into()).into() } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs new file mode 100644 index 0000000..2c34b14 --- /dev/null +++ b/proc/src/proc_expose.rs @@ -0,0 +1,392 @@ +use crate::*; +use syn::parse::discouraged::Speculative; + +pub(crate) fn expose_impl (meta: TokenStream, data: TokenStream) -> TokenStream { + let mut out = TokenStream2::new(); + ExposeDefinition { + meta: parse_macro_input!(meta as ExposeMeta), + data: parse_macro_input!(data as ExposeImpl), + }.to_tokens(&mut out); + out.into() +} + +#[derive(Debug, Clone)] +struct ExposeDefinition { + meta: ExposeMeta, + data: ExposeImpl, +} + +impl Parse for ExposeDefinition { + fn parse (input: ParseStream) -> Result { + Ok(Self { + meta: input.parse::()?, + data: input.parse::()?, + }) + } +} + +impl ToTokens for ExposeDefinition { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self { meta, data } = self; + for token in quote! { #data } { + out.append(token) + } + } +} + +#[derive(Debug, Clone)] +struct ExposeMeta {} + +impl Parse for ExposeMeta { + fn parse (input: ParseStream) -> Result { + Ok(Self {}) + } +} + +#[derive(Debug, Clone)] +struct ExposeImpl { + target: Ident, + items: Vec, + types: BTreeMap>, +} + +impl Parse for ExposeImpl { + fn parse (input: ParseStream) -> Result { + let _impl = input.parse::()?; + let target = input.parse::()?; + let group; + let brace = braced!(group in input); + let mut items = vec![]; + let mut types: BTreeMap> = Default::default(); + while !group.is_empty() { + let fork = group.fork(); + if let Ok(block) = fork.parse::() { + let t = block.type_.into(); + if let Some(values) = types.get_mut(&t) { + for (key, value) in block.values.into_iter() { + if values.contains_key(&key) { + return Err(input.error(format!("{key:?} ({t:?}): already exists"))) + } else { + values.insert(key, value); + } + } + } else { + types.insert(t, block.values); + } + group.advance_to(&fork); + continue + } + let fork = group.fork(); + if let Ok(item) = fork.parse::() { + items.push(item); + group.advance_to(&fork); + continue + } + return Err(input.error( + "expected either item or #[tengri::expose(type)] { \":key\" => value }" + )); + } + Ok(Self { target, items, types }) + } +} + +impl ToTokens for ExposeImpl { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self { target, items, types } = self; + for token in quote! { impl #target { #(#items)* } } { + out.append(token); + } + for (t, variants) in types.iter() { + let predef = match format!("{}", quote! { #t }).as_str() { + "bool" => vec![ + quote! { ::tengri::dsl::Value::Sym(":true") => true }, + quote! { ::tengri::dsl::Value::Sym(":false") => false }, + ], + "u8" | "u16" | "u32" | "u64" | "usize" | + "i8" | "i16" | "i32" | "i64" | "isize" => vec![ + quote! { ::tengri::dsl::Value::Num(n) => *n }, + ], + _ => vec![], + }; + let values = variants.values(); + let trait_impl = quote! { + impl ::tengri::dsl::Context<#t> for #target { + fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { + Some(match dsl { + #(#predef,)* + #(#values,)* + _ => return None + }) + } + } + }; + for token in trait_impl { + out.append(token); + } + } + } +} + +#[derive(Debug, Clone)] +struct ExposeBlock { + type_: Type, + values: BTreeMap, +} + +impl Parse for ExposeBlock { + fn parse (input: ParseStream) -> Result { + let _ = input.parse::()?; + + let group; + let bracket = bracketed!(group in input); + let path = group.parse::()?; + let type_ = if + path.segments.get(0).map(|x|x.ident.to_string()) == Some("tengri".to_string()) && + path.segments.get(1).map(|x|x.ident.to_string()) == Some("expose".to_string()) + { + let token; + let paren = parenthesized!(token in group); + token.parse::()? + } else { + return Err(input.error("expected #[tengri::expose(type)]")) + }; + + let group; + let brace = braced!(group in input); + let mut values = BTreeMap::new(); + while !group.is_empty() { + let arm = group.parse::()?; + values.insert(arm.key.clone(), arm); + let _ = group.parse::()?; + } + Ok(Self { type_, values }) + } +} + +#[derive(Debug, Clone)] +struct ExposeArm { + key: ExposeSym, + value: Expr +} + +impl Parse for ExposeArm { + fn parse (input: ParseStream) -> Result { + let key = input.parse::()?.into(); + let _ = input.parse::()?; + let _ = input.parse::]>()?; + let value = input.parse::()?; + Ok(Self { key, value }) + } +} + +impl ToTokens for ExposeArm { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self { key, value } = self; + 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(key.0.token()); + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + for token in quote! { #value } { + out.append(token); + } + } +} + +#[derive(Debug, Clone)] +struct ExposeSym(LitStr); + +impl From for ExposeSym { + fn from (this: LitStr) -> Self { + Self(this) + } +} + +impl PartialOrd for ExposeSym { + fn partial_cmp (&self, other: &Self) -> Option { + let this = &self.0; + let that = &other.0; + Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))) + } +} + +impl Ord for ExposeSym { + fn cmp (&self, other: &Self) -> Ordering { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })) + } +} + +impl PartialEq for ExposeSym { + fn eq (&self, other: &Self) -> bool { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }) == format!("{}", quote! { #that }) + } +} + +impl Eq for ExposeSym {} + +#[derive(Debug, Clone)] +struct ExposeType(Type); + +impl From for ExposeType { + fn from (this: Type) -> Self { + Self(this) + } +} + +impl PartialOrd for ExposeType { + fn partial_cmp (&self, other: &Self) -> Option { + let this = &self.0; + let that = &other.0; + Some(format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that }))) + } +} + +impl Ord for ExposeType { + fn cmp (&self, other: &Self) -> Ordering { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }).cmp(&format!("{}", quote! { #that })) + } +} + +impl PartialEq for ExposeType { + fn eq (&self, other: &Self) -> bool { + let this = &self.0; + let that = &other.0; + format!("{}", quote! { #this }) == format!("{}", quote! { #that }) + } +} + +impl Eq for ExposeType {} + +impl ToTokens for ExposeType { + fn to_tokens (&self, out: &mut TokenStream2) { + self.0.to_tokens(out) + } +} + +#[cfg(test)] #[test] fn test_expose_definition () { + let parsed: ExposeImpl = pq! { + //#[tengri_proc::expose] + impl Something { + #[tengri::expose(bool)] { + ":bool1" => true || false, + } + fn something () {} + } + }; + // FIXME: + //assert_eq!( + //format!("{}", quote! { #parsed }), + //format!("{}", quote! { + //impl Something { + //fn something () {} + //} + //impl ::tengri::Context for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option { + //Some(match dsl { + //::tengri::Value::Sym(":true") => true, + //::tengri::Value::Sym(":false") => false, + //::tengri::Value::Sym(":bool1") => true || false, + //_ => return None + //}) + //} + //} + //}) + //); + + let parsed: ExposeImpl = pq! { + //#[tengri_proc::expose] + impl Something { + #[tengri::expose(bool)] { + ":bool1" => true || false, + } + #[tengri::expose(u16)] { + ":u161" => 0 + 1, + } + #[tengri::expose(usize)] { + ":usize1" => 1 + 2, + } + #[tengri::expose(Arc)] { + ":arcstr1" => "foo".into(), + } + #[tengri::expose(Option>)] { + ":optarcstr1" => Some("bar".into()), + ":optarcstr2" => Some("baz".into()), + } + fn something () {} + } + }; + // FIXME: + //assert_eq!( + //format!("{}", quote! { #parsed }), + //format!("{}", quote! { + //impl Something { + //fn something () {} + //} + //impl ::tengri::Context> for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option> { + //Some(match dsl { + //::tengri::Value::Sym(":arcstr1") => "foo".into(), + //_ => return None + //}) + //} + //} + //impl ::tengri::Context>> for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option>> { + //Some(match dsl { + //::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), + //::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), + //_ => return None + //}) + //} + //} + //impl ::tengri::Context for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option { + //Some(match dsl { + //::tengri::Value::Sym(":true") => true, + //::tengri::Value::Sym(":false") => false, + //::tengri::Value::Sym(":bool1") => true || false, + //_ => return None + //}) + //} + //} + //impl ::tengri::Context for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option { + //Some(match dsl { + //::tengri::Value::Num(n) => *n as u16, + //::tengri::Value::Sym(":u161") => 0 + 1, + //_ => return None + //}) + //} + //} + //impl ::tengri::Context for Something { + //fn get (&self, dsl: &::tengri::Value) -> Option { + //Some(match dsl { + //::tengri::Value::Num(n) => *n as usize, + //::tengri::Value::Sym(":usize1") => 1 + 2, + //_ => return None + //}) + //} + //} + //}) + //) +} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 7095fce..2a32d27 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -1,14 +1,4 @@ -use proc_macro::TokenStream; -use proc_macro2::{ - TokenStream as TokenStream2, TokenTree, - Ident, Span, Punct, Spacing, Group, Delimiter, Literal -}; -use syn::{parse, parse_macro_input, braced, Token}; -use syn::{Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr}; -use syn::parse::{Parse, ParseStream, Result}; -use syn::token::{PathSep, Brace}; -use syn::punctuated::Punctuated; -use quote::{quote, TokenStreamExt, ToTokens}; +use crate::*; pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { let mut out = TokenStream2::new(); @@ -28,7 +18,6 @@ struct ViewDefinition { #[derive(Debug, Clone)] struct ViewMeta { output: Ident, - //attrs: Vec, } #[derive(Debug, Clone)] @@ -70,7 +59,7 @@ impl Parse for ViewMeta { impl Parse for ViewImpl { fn parse (input: ParseStream) -> Result { let _ = input.parse::()?; - let mut syms = vec![]; + let mut syms = vec![]; Ok(Self { target: input.parse::()?, items: { @@ -131,7 +120,6 @@ impl Parse for ViewItem { impl ToTokens for ViewSym { fn to_tokens (&self, out: &mut TokenStream2) { - use Spacing::*; out.append(Punct::new(':', Joint)); out.append(Punct::new(':', Alone)); out.append(Ident::new("tengri", Span::call_site())); @@ -226,8 +214,6 @@ impl std::cmp::PartialEq for ViewSym { } } -#[cfg(test)] use syn::{ItemImpl, parse_quote as pq}; - #[cfg(test)] #[test] fn test_view_meta () { let x: ViewMeta = pq! { SomeOutput }; let output: Ident = pq! { SomeOutput }; From 7570aefcc218ce0e7d2a6a6a45d696675c87ec63 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 6 May 2025 00:48:14 +0300 Subject: [PATCH 035/178] proc: simplify expose macro --- Cargo.lock | 1 + proc/Cargo.toml | 5 +- proc/src/lib.rs | 24 ++- proc/src/proc_expose.rs | 393 ++++++++++++++++------------------------ proc/src/proc_view.rs | 270 ++++++++++++--------------- 5 files changed, 288 insertions(+), 405 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e5ce2b..0df7fbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -976,6 +976,7 @@ dependencies = [ name = "tengri_proc" version = "0.13.0" dependencies = [ + "heck", "proc-macro2", "quote", "syn", diff --git a/proc/Cargo.toml b/proc/Cargo.toml index aa2cd24..6001947 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -8,6 +8,7 @@ edition = { workspace = true } proc-macro = true [dependencies] -syn = { version = "2", features = ["full", "extra-traits"] } -quote = { version = "1" } +syn = { version = "2", features = ["full", "extra-traits"] } +quote = { version = "1" } proc-macro2 = { version = "1", features = ["span-locations"] } +heck = { version = "0.5" } diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 52c8814..16dd8f0 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,6 +1,6 @@ extern crate proc_macro; -pub(crate) use std::collections::{HashMap, BTreeMap}; +pub(crate) use std::collections::{BTreeMap, BTreeSet}; pub(crate) use std::cmp::Ordering; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ @@ -10,22 +10,38 @@ pub(crate) use proc_macro2::{ pub(crate) use syn::{ parse, parse_macro_input, parse_quote as pq, braced, bracketed, parenthesized, Token, - Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, ImplItem, LitStr, Type, + Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, + ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, parse::{Parse, ParseStream, Result}, token::{PathSep, Brace}, punctuated::Punctuated, }; pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; +pub(crate) use heck::AsKebabCase; mod proc_view; mod proc_expose; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { - self::proc_view::view_impl(meta.into(), item.into()).into() + use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; + write_macro(ViewDef( + parse_macro_input!(meta as ViewMeta), + parse_macro_input!(item as ViewImpl), + )) } #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { - self::proc_expose::expose_impl(meta.into(), item.into()).into() + use self::proc_expose::{ExposeDef, ExposeMeta, ExposeImpl}; + write_macro(ExposeDef( + parse_macro_input!(meta as ExposeMeta), + parse_macro_input!(item as ExposeImpl), + )) +} + +fn write_macro (t: T) -> TokenStream { + let mut out = TokenStream2::new(); + t.to_tokens(&mut out); + out.into() } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 2c34b14..bf7ae58 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -1,103 +1,76 @@ use crate::*; -use syn::parse::discouraged::Speculative; -pub(crate) fn expose_impl (meta: TokenStream, data: TokenStream) -> TokenStream { - let mut out = TokenStream2::new(); - ExposeDefinition { - meta: parse_macro_input!(meta as ExposeMeta), - data: parse_macro_input!(data as ExposeImpl), - }.to_tokens(&mut out); - out.into() +#[derive(Debug, Clone)] +pub(crate) struct ExposeDef(pub(crate) ExposeMeta, pub(crate) ExposeImpl); + +#[derive(Debug, Clone)] +pub(crate) struct ExposeMeta; + +#[derive(Debug, Clone)] +pub(crate) struct ExposeImpl { + block: ItemImpl, + exposed: BTreeMap>, } #[derive(Debug, Clone)] -struct ExposeDefinition { - meta: ExposeMeta, - data: ExposeImpl, -} +struct ExposeArm(String, Ident); -impl Parse for ExposeDefinition { +#[derive(Debug, Clone)] +struct ExposeSym(LitStr); + +#[derive(Debug, Clone)] +struct ExposeType(Box); + +impl Parse for ExposeMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - meta: input.parse::()?, - data: input.parse::()?, - }) + Ok(Self) } } -impl ToTokens for ExposeDefinition { +impl Parse for ExposeImpl { + fn parse (input: ParseStream) -> Result { + let block = input.parse::()?; + let mut exposed: BTreeMap> = Default::default(); + for item in block.items.iter() { + if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, output, .. }, .. }) = item { + if let ReturnType::Type(_, return_type) = output { + let return_type = ExposeType(return_type.clone()); + if !exposed.contains_key(&return_type) { + exposed.insert(return_type.clone(), Default::default()); + } + let values = exposed.get_mut(&return_type).unwrap(); + let key = format!(":{}", AsKebabCase(format!("{}", &ident))); + if values.contains_key(&key) { + return Err(input.error(format!("already defined: {key}"))) + } + values.insert(key, ident.clone()); + } else { + return Err(input.error("output type must be specified")) + } + } + } + Ok(Self { block, exposed }) + } +} + +impl ToTokens for ExposeDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self { meta, data } = self; + let Self(meta, data) = self; for token in quote! { #data } { out.append(token) } } } -#[derive(Debug, Clone)] -struct ExposeMeta {} - -impl Parse for ExposeMeta { - fn parse (input: ParseStream) -> Result { - Ok(Self {}) - } -} - -#[derive(Debug, Clone)] -struct ExposeImpl { - target: Ident, - items: Vec, - types: BTreeMap>, -} - -impl Parse for ExposeImpl { - fn parse (input: ParseStream) -> Result { - let _impl = input.parse::()?; - let target = input.parse::()?; - let group; - let brace = braced!(group in input); - let mut items = vec![]; - let mut types: BTreeMap> = Default::default(); - while !group.is_empty() { - let fork = group.fork(); - if let Ok(block) = fork.parse::() { - let t = block.type_.into(); - if let Some(values) = types.get_mut(&t) { - for (key, value) in block.values.into_iter() { - if values.contains_key(&key) { - return Err(input.error(format!("{key:?} ({t:?}): already exists"))) - } else { - values.insert(key, value); - } - } - } else { - types.insert(t, block.values); - } - group.advance_to(&fork); - continue - } - let fork = group.fork(); - if let Ok(item) = fork.parse::() { - items.push(item); - group.advance_to(&fork); - continue - } - return Err(input.error( - "expected either item or #[tengri::expose(type)] { \":key\" => value }" - )); - } - Ok(Self { target, items, types }) - } -} - impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { - let Self { target, items, types } = self; - for token in quote! { impl #target { #(#items)* } } { + let Self { block, exposed } = self; + let target = &self.block.self_ty; + for token in quote! { #block } { out.append(token); } - for (t, variants) in types.iter() { - let predef = match format!("{}", quote! { #t }).as_str() { + for (t, variants) in exposed.iter() { + let predefined = match format!("{}", quote! { #t }).as_str() { "bool" => vec![ quote! { ::tengri::dsl::Value::Sym(":true") => true }, quote! { ::tengri::dsl::Value::Sym(":false") => false }, @@ -113,7 +86,7 @@ impl ToTokens for ExposeImpl { impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { Some(match dsl { - #(#predef,)* + #(#predefined,)* #(#values,)* _ => return None }) @@ -127,61 +100,9 @@ impl ToTokens for ExposeImpl { } } -#[derive(Debug, Clone)] -struct ExposeBlock { - type_: Type, - values: BTreeMap, -} - -impl Parse for ExposeBlock { - fn parse (input: ParseStream) -> Result { - let _ = input.parse::()?; - - let group; - let bracket = bracketed!(group in input); - let path = group.parse::()?; - let type_ = if - path.segments.get(0).map(|x|x.ident.to_string()) == Some("tengri".to_string()) && - path.segments.get(1).map(|x|x.ident.to_string()) == Some("expose".to_string()) - { - let token; - let paren = parenthesized!(token in group); - token.parse::()? - } else { - return Err(input.error("expected #[tengri::expose(type)]")) - }; - - let group; - let brace = braced!(group in input); - let mut values = BTreeMap::new(); - while !group.is_empty() { - let arm = group.parse::()?; - values.insert(arm.key.clone(), arm); - let _ = group.parse::()?; - } - Ok(Self { type_, values }) - } -} - -#[derive(Debug, Clone)] -struct ExposeArm { - key: ExposeSym, - value: Expr -} - -impl Parse for ExposeArm { - fn parse (input: ParseStream) -> Result { - let key = input.parse::()?.into(); - let _ = input.parse::()?; - let _ = input.parse::]>()?; - let value = input.parse::()?; - Ok(Self { key, value }) - } -} - impl ToTokens for ExposeArm { fn to_tokens (&self, out: &mut TokenStream2) { - let Self { key, value } = self; + let Self(key, value) = self; out.append(Punct::new(':', Joint)); out.append(Punct::new(':', Alone)); out.append(Ident::new("tengri", Span::call_site())); @@ -196,7 +117,7 @@ impl ToTokens for ExposeArm { out.append(Ident::new("Sym", Span::call_site())); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); - out.append(key.0.token()); + out.append(LitStr::new(&key, Span::call_site()).token()); out })); out.append(Punct::new('=', Joint)); @@ -207,9 +128,6 @@ impl ToTokens for ExposeArm { } } -#[derive(Debug, Clone)] -struct ExposeSym(LitStr); - impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) @@ -242,12 +160,9 @@ impl PartialEq for ExposeSym { impl Eq for ExposeSym {} -#[derive(Debug, Clone)] -struct ExposeType(Type); - impl From for ExposeType { fn from (this: Type) -> Self { - Self(this) + Self(Box::new(this)) } } @@ -284,109 +199,107 @@ impl ToTokens for ExposeType { } #[cfg(test)] #[test] fn test_expose_definition () { - let parsed: ExposeImpl = pq! { - //#[tengri_proc::expose] - impl Something { - #[tengri::expose(bool)] { - ":bool1" => true || false, - } - fn something () {} - } - }; - // FIXME: - //assert_eq!( - //format!("{}", quote! { #parsed }), - //format!("{}", quote! { - //impl Something { - //fn something () {} - //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Sym(":true") => true, - //::tengri::Value::Sym(":false") => false, - //::tengri::Value::Sym(":bool1") => true || false, - //_ => return None - //}) - //} - //} - //}) - //); + // TODO + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //fn something () -> bool {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////); - let parsed: ExposeImpl = pq! { - //#[tengri_proc::expose] - impl Something { - #[tengri::expose(bool)] { - ":bool1" => true || false, - } - #[tengri::expose(u16)] { - ":u161" => 0 + 1, - } - #[tengri::expose(usize)] { - ":usize1" => 1 + 2, - } - #[tengri::expose(Arc)] { - ":arcstr1" => "foo".into(), - } - #[tengri::expose(Option>)] { - ":optarcstr1" => Some("bar".into()), - ":optarcstr2" => Some("baz".into()), - } - fn something () {} - } - }; - // FIXME: - //assert_eq!( - //format!("{}", quote! { #parsed }), - //format!("{}", quote! { - //impl Something { - //fn something () {} + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //#[tengri::expose(bool)] { + //":bool1" => true || false, //} - //impl ::tengri::Context> for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option> { - //Some(match dsl { - //::tengri::Value::Sym(":arcstr1") => "foo".into(), - //_ => return None - //}) - //} + //#[tengri::expose(u16)] { + //":u161" => 0 + 1, //} - //impl ::tengri::Context>> for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option>> { - //Some(match dsl { - //::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), - //::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), - //_ => return None - //}) - //} + //#[tengri::expose(usize)] { + //":usize1" => 1 + 2, //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Sym(":true") => true, - //::tengri::Value::Sym(":false") => false, - //::tengri::Value::Sym(":bool1") => true || false, - //_ => return None - //}) - //} + //#[tengri::expose(Arc)] { + //":arcstr1" => "foo".into(), //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Num(n) => *n as u16, - //::tengri::Value::Sym(":u161") => 0 + 1, - //_ => return None - //}) - //} + //#[tengri::expose(Option>)] { + //":optarcstr1" => Some("bar".into()), + //":optarcstr2" => Some("baz".into()), //} - //impl ::tengri::Context for Something { - //fn get (&self, dsl: &::tengri::Value) -> Option { - //Some(match dsl { - //::tengri::Value::Num(n) => *n as usize, - //::tengri::Value::Sym(":usize1") => 1 + 2, - //_ => return None - //}) - //} - //} - //}) - //) + //fn something () {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option> { + ////Some(match dsl { + ////::tengri::Value::Sym(":arcstr1") => "foo".into(), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context>> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option>> { + ////Some(match dsl { + ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), + ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as u16, + ////::tengri::Value::Sym(":u161") => 0 + 1, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as usize, + ////::tengri::Value::Sym(":usize1") => 1 + 2, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////) } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 2a32d27..c70cb31 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -1,52 +1,20 @@ use crate::*; -pub(crate) fn view_impl (meta: TokenStream, data: TokenStream) -> TokenStream { - let mut out = TokenStream2::new(); - ViewDefinition { - meta: parse_macro_input!(meta as ViewMeta), - data: parse_macro_input!(data as ViewImpl), - }.to_tokens(&mut out); - out.into() -} +#[derive(Debug, Clone)] +pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); #[derive(Debug, Clone)] -struct ViewDefinition { - meta: ViewMeta, - data: ViewImpl, -} - -#[derive(Debug, Clone)] -struct ViewMeta { +pub(crate) struct ViewMeta { output: Ident, } #[derive(Debug, Clone)] -struct ViewImpl { - target: Ident, - items: Vec, - syms: Vec, +pub(crate) struct ViewImpl { + block: ItemImpl, + exposed: BTreeMap, } -#[derive(Debug, Clone)] -struct ViewItem { - item: ImplItem, - expose: Option, -} - -#[derive(Debug, Clone)] -struct ViewSym { - symbol: Literal, - name: Ident, -} - -impl Parse for ViewDefinition { - fn parse (input: ParseStream) -> Result { - Ok(Self { - meta: input.parse::()?, - data: input.parse::()?, - }) - } -} +struct ViewArm(String, Ident); impl Parse for ViewMeta { fn parse (input: ParseStream) -> Result { @@ -58,68 +26,52 @@ impl Parse for ViewMeta { impl Parse for ViewImpl { fn parse (input: ParseStream) -> Result { - let _ = input.parse::()?; - let mut syms = vec![]; - Ok(Self { - target: input.parse::()?, - items: { - let group; - let brace = braced!(group in input); - let mut items = vec![]; - while !group.is_empty() { - let item = group.parse::()?; - if let Some(expose) = &item.expose { - if let ImplItem::Fn(ref item) = item.item { - let symbol = expose.clone(); - let name = item.sig.ident.clone(); - syms.push(ViewSym { symbol, name }) - } else { - return Err( - input.error("only fn items can be exposed to #[tengri::view]") - ) - } - } - items.push(item); + let block = input.parse::()?; + let mut exposed: BTreeMap = Default::default(); + for item in block.items.iter() { + if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, .. }, .. }) = item { + let key = format!(":{}", AsKebabCase(format!("{}", &ident))); + if exposed.contains_key(&key) { + return Err(input.error(format!("already defined: {ident}"))); } - items - }, - syms, - }) + exposed.insert(key, ident.clone()); + } + } + Ok(Self { block, exposed }) } } - -impl Parse for ViewItem { - fn parse (input: ParseStream) -> Result { - let mut expose = None; - Ok(Self { - item: { - let mut item = input.parse::()?; - if let ImplItem::Fn(ref mut item) = item { - item.attrs = item.attrs.iter().filter(|attr| { - if let Attribute { - meta: Meta::List(MetaList { path, tokens, .. }), .. - } = attr - && path.segments.len() == 2 - && nth_segment_is(&path.segments, 0, "tengri") - && nth_segment_is(&path.segments, 1, "view") - && let Some(TokenTree::Literal(name)) = tokens.clone().into_iter().next() - { - expose = Some(name); - return false - } - true - }).map(|x|x.clone()).collect(); - }; - item - }, - expose, - }) - } -} - -impl ToTokens for ViewSym { +impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let ident = &block.self_ty; + let exposed: Vec<_> = exposed.iter().map(|(k,v)|ViewArm(k.clone(), v.clone())).collect(); + for token in quote! { + #block + /// Generated by [tengri_proc]. + impl ::tengri::output::Content<#output> for #ident { + fn content (&self) -> impl Render<#output> { + self.size.of(::tengri::output::View(self, self.config.view)) + } + } + /// Generated by [tengri_proc]. + impl<'a> ::tengri::output::ViewContext<'a, #output> for #ident { + fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + match value { + #(#exposed)* + _ => panic!("expected Sym(content), got: {value:?}") + } + } + } + } { + out.append(token) + } + } +} + +impl ToTokens for ViewArm { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(key, value) = self; out.append(Punct::new(':', Joint)); out.append(Punct::new(':', Alone)); out.append(Ident::new("tengri", Span::call_site())); @@ -134,7 +86,7 @@ impl ToTokens for ViewSym { out.append(Ident::new("Sym", Span::call_site())); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); - out.append(self.symbol.clone()); + out.append(LitStr::new(key, Span::call_site()).token()); out })); out.append(Punct::new('=', Joint)); @@ -144,7 +96,7 @@ impl ToTokens for ViewSym { let mut out = TokenStream2::new(); out.append(Ident::new("self", Span::call_site())); out.append(Punct::new('.', Alone)); - out.append(self.name.clone()); + out.append(value.clone()); out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); out.append(Punct::new('.', Alone)); out.append(Ident::new("boxed", Span::call_site())); @@ -155,43 +107,42 @@ impl ToTokens for ViewSym { } } -impl ToTokens for ViewItem { - fn to_tokens (&self, out: &mut TokenStream2) { - self.item.to_tokens(out) - } -} - -impl ToTokens for ViewDefinition { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self { - meta: ViewMeta { output }, - data: ViewImpl { target, syms, items }, - } = self; - for token in quote! { - /// Augmented by [tengri_proc]. - impl #target { - #(#items)* - } - /// Generated by [tengri_proc]. - impl ::tengri::output::Content<#output> for #target { - fn content (&self) -> impl Render<#output> { - self.size.of(::tengri::output::View(self, self.config.view)) - } - } - /// Generated by [tengri_proc]. - impl<'a> ::tengri::output::ViewContext<'a, #output> for #target { - fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { - match value { - #(#syms)* - _ => panic!("expected Sym(content), got: {value:?}") - } - } - } - } { - out.append(token) - } - } -} +//impl ToTokens for ViewSym { + //fn to_tokens (&self, out: &mut TokenStream2) { + //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(self.symbol.clone()); + //out + //})); + //out.append(Punct::new('=', Joint)); + //out.append(Punct::new('>', Alone)); + //out.append(Ident::new("Some", Span::call_site())); + //out.append(Group::new(Delimiter::Parenthesis, { + //let mut out = TokenStream2::new(); + //out.append(Ident::new("self", Span::call_site())); + //out.append(Punct::new('.', Alone)); + //out.append(self.name.clone()); + //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + //out.append(Punct::new('.', Alone)); + //out.append(Ident::new("boxed", Span::call_site())); + //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + //out + //})); + //out.append(Punct::new(',', Alone)); + //} +//} fn nth_segment_is (segments: &Punctuated, n: usize, x: &str) -> bool { if let Some(PathSegment { arguments: PathArguments::None, ident, .. }) = segments.get(n) { @@ -202,17 +153,17 @@ fn nth_segment_is (segments: &Punctuated, n: usize, x: &st return false } -impl std::cmp::PartialEq for ViewItem { - fn eq (&self, other: &Self) -> bool { - self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) - } -} +//impl std::cmp::PartialEq for ViewItem { + //fn eq (&self, other: &Self) -> bool { + //self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) + //} +//} -impl std::cmp::PartialEq for ViewSym { - fn eq (&self, other: &Self) -> bool { - self.name == other.name && (format!("{}", self.symbol) == format!("{}", other.symbol)) - } -} +//impl std::cmp::PartialEq for ViewSym { + //fn eq (&self, other: &Self) -> bool { + //self.name == other.name && (format!("{}", self.symbol) == format!("{}", other.symbol)) + //} +//} #[cfg(test)] #[test] fn test_view_meta () { let x: ViewMeta = pq! { SomeOutput }; @@ -221,6 +172,7 @@ impl std::cmp::PartialEq for ViewSym { } #[cfg(test)] #[test] fn test_view_impl () { + // TODO let x: ViewImpl = pq! { impl Foo { /// docstring1 @@ -232,20 +184,20 @@ impl std::cmp::PartialEq for ViewSym { } }; let expected_target: Ident = pq! { Foo }; - assert_eq!(x.target, expected_target); - assert_eq!(x.items.len(), 2); - assert_eq!(x.items[0].item, pq! { - /// docstring1 - #[bar] fn a_view () {} - }); - assert_eq!(x.items[1].item, pq! { - #[baz] - /// docstring2 - #[baz] fn is_not_view () {} - }); - assert_eq!(x.syms, vec![ - ViewSym { symbol: pq! { ":view1" }, name: pq! { a_view }, }, - ]); + //assert_eq!(x.target, expected_target); + //assert_eq!(x.items.len(), 2); + //assert_eq!(x.items[0].item, pq! { + ///// docstring1 + //#[bar] fn a_view () {} + //}); + //assert_eq!(x.items[1].item, pq! { + //#[baz] + ///// docstring2 + //#[baz] fn is_not_view () {} + //}); + //assert_eq!(x.syms, vec![ + //ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, }, + //]); } #[cfg(test)] #[test] fn test_view_definition () { From 7df7cb839c14c0e010ce36519c75ffacc0e76c18 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 6 May 2025 21:33:53 +0300 Subject: [PATCH 036/178] wip: proc: command macro --- proc/src/lib.rs | 15 +++- proc/src/proc_command.rs | 144 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 proc/src/proc_command.rs diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 16dd8f0..38c8b15 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; pub(crate) use std::collections::{BTreeMap, BTreeSet}; pub(crate) use std::cmp::Ordering; +pub(crate) use std::sync::Arc; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ TokenStream as TokenStream2, TokenTree, @@ -11,16 +12,17 @@ pub(crate) use syn::{ parse, parse_macro_input, parse_quote as pq, braced, bracketed, parenthesized, Token, Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, - ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, + ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, PatType, parse::{Parse, ParseStream, Result}, token::{PathSep, Brace}, punctuated::Punctuated, }; pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; -pub(crate) use heck::AsKebabCase; +pub(crate) use heck::{AsKebabCase, AsUpperCamelCase}; mod proc_view; mod proc_expose; +mod proc_command; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { @@ -40,6 +42,15 @@ pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { )) } +#[proc_macro_attribute] +pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream { + use self::proc_command::{CommandDef, CommandMeta, CommandImpl}; + write_macro(CommandDef( + parse_macro_input!(meta as CommandMeta), + parse_macro_input!(item as CommandImpl), + )) +} + fn write_macro (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs new file mode 100644 index 0000000..ac1e2a9 --- /dev/null +++ b/proc/src/proc_command.rs @@ -0,0 +1,144 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); + +#[derive(Debug, Clone)] +pub(crate) struct CommandMeta { + target: Ident, +} + +#[derive(Debug, Clone)] +pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); + +#[derive(Debug, Clone)] +struct CommandVariant(Ident, Vec); + +#[derive(Debug, Clone)] +struct CommandArm(Arc, Ident, Vec, ReturnType); + +impl Parse for CommandMeta { + fn parse (input: ParseStream) -> Result { + Ok(Self { + target: input.parse::()?, + }) + } +} + +impl Parse for CommandImpl { + fn parse (input: ParseStream) -> Result { + let block = input.parse::()?; + let mut exposed: BTreeMap, CommandArm> = Default::default(); + for item in block.items.iter() { + if let ImplItem::Fn(ImplItemFn { + sig: Signature { ident, inputs, output, .. }, .. + }) = item { + let key: Arc = + format!("{}", AsKebabCase(format!("{}", &ident))).into(); + let variant: Arc = + format!("{}", AsUpperCamelCase(format!("{}", &ident))).into(); + if exposed.contains_key(&key) { + return Err(input.error(format!("already defined: {ident}"))); + } + exposed.insert(key, CommandArm( + variant, + ident.clone(), + inputs.iter().map(|x|x.clone()).collect(), + output.clone(), + )); + } + } + Ok(Self(block, exposed)) + } +} + +impl ToTokens for CommandDef { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(CommandMeta { target }, CommandImpl(block, exposed)) = self; + let enumeration = &block.self_ty; + let definitions = exposed.values().map(|x|CommandVariant( + x.1.clone(), + x.2.clone(), + )); + let implementations = exposed.values().map(|x|CommandArm( + x.0.clone(), + x.1.clone(), + x.2.clone(), + x.3.clone(), + )); + for token in quote! { + #block + enum #enumeration { + #(#definitions)* + } + impl Command<#target> for #enumeration { + fn execute (self, state: &mut #target) -> Perhaps { + match self { + #(#implementations)* + } + } + } + } { + out.append(token) + } + } +} + +impl ToTokens for CommandVariant { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(ident, args) = self; + out.append(LitStr::new(&format!("{}", ident), Span::call_site()) + .token()); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + out.append(LitStr::new( + &format!("{}", quote! { #ty }), + Span::call_site() + ).token()); + out.append(Punct::new(',', Alone)); + } + } + out + })); + out.append(Punct::new(',', Alone)); + } +} + +impl ToTokens for CommandArm { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(symbol, ident, args, returnType) = self; + 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(); + for arg in args.iter() { + out.append(LitStr::new(&symbol, Span::call_site()).token()); + } + out + })); + out.append(Punct::new('=', Joint)); + out.append(Punct::new('>', Alone)); + out.append(LitStr::new(&format!("{}", ident), Span::call_site()).token()); + out.append(Group::new(Delimiter::Parenthesis, { + let mut out = TokenStream2::new(); + for arg in args.iter() { + // TODO + //out.append(LitStr::new(&symbol, Span::call_site()).token()); + } + out + })); + out.append(Punct::new(',', Alone)); + } +} From c56b08c24e83f65ddee762b54f8d865079d58ce9 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 7 May 2025 12:37:38 +0300 Subject: [PATCH 037/178] proc: expose: fix output match statement --- proc/src/proc_command.rs | 4 +++- proc/src/proc_expose.rs | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index ac1e2a9..8b16dec 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -92,7 +92,9 @@ impl ToTokens for CommandVariant { out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); for arg in args.iter() { - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + if let FnArg::Typed(PatType { + attrs, pat, colon_token, ty + }) = arg { out.append(LitStr::new( &format!("{}", quote! { #ty }), Span::call_site() diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index bf7ae58..9315f2e 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -81,7 +81,7 @@ impl ToTokens for ExposeImpl { ], _ => vec![], }; - let values = variants.values(); + let values = variants.iter().map(|(k, v)|ExposeArm(k.clone(), v.clone())); let trait_impl = quote! { impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { @@ -97,6 +97,9 @@ impl ToTokens for ExposeImpl { out.append(token); } } + //if exposed.len() > 0 { + //panic!("{}", quote! {#out}); + //} } } @@ -122,9 +125,12 @@ impl ToTokens for ExposeArm { })); out.append(Punct::new('=', Joint)); out.append(Punct::new('>', Alone)); + out.append(Ident::new("self", Span::call_site())); + out.append(Punct::new('.', Alone)); for token in quote! { #value } { out.append(token); } + out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); } } From 751e01a41e2b20561489d36ca64f9f6af6ee1f00 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 7 May 2025 14:36:14 +0300 Subject: [PATCH 038/178] : wip: proc: command (pt.3) --- proc/src/lib.rs | 24 ++++- proc/src/proc_command.rs | 209 ++++++++++++++++++++++++++++----------- 2 files changed, 172 insertions(+), 61 deletions(-) diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 38c8b15..82cb6c8 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(str_as_str)] + extern crate proc_macro; pub(crate) use std::collections::{BTreeMap, BTreeSet}; @@ -27,7 +29,7 @@ mod proc_command; #[proc_macro_attribute] pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; - write_macro(ViewDef( + write(ViewDef( parse_macro_input!(meta as ViewMeta), parse_macro_input!(item as ViewImpl), )) @@ -36,7 +38,7 @@ pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { use self::proc_expose::{ExposeDef, ExposeMeta, ExposeImpl}; - write_macro(ExposeDef( + write(ExposeDef( parse_macro_input!(meta as ExposeMeta), parse_macro_input!(item as ExposeImpl), )) @@ -45,14 +47,28 @@ pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream { use self::proc_command::{CommandDef, CommandMeta, CommandImpl}; - write_macro(CommandDef( + write(CommandDef( parse_macro_input!(meta as CommandMeta), parse_macro_input!(item as CommandImpl), )) } -fn write_macro (t: T) -> TokenStream { +pub(crate) fn write (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); out.into() } + +pub(crate) fn write_quote (quote: TokenStream2) -> TokenStream2 { + let mut out = TokenStream2::new(); + for token in quote { + out.append(token); + } + out +} + +pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { + for token in quote { + out.append(token); + } +} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 8b16dec..8ad7776 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -4,73 +4,79 @@ use crate::*; pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); #[derive(Debug, Clone)] -pub(crate) struct CommandMeta { - target: Ident, -} +pub(crate) struct CommandMeta(Ident); #[derive(Debug, Clone)] pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); #[derive(Debug, Clone)] -struct CommandVariant(Ident, Vec); +struct CommandArm(Ident, Vec, ReturnType); #[derive(Debug, Clone)] -struct CommandArm(Arc, Ident, Vec, ReturnType); +struct CommandVariant(Ident, Vec); impl Parse for CommandMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - target: input.parse::()?, - }) + Ok(Self(input.parse::()?)) } } impl Parse for CommandImpl { fn parse (input: ParseStream) -> Result { - let block = input.parse::()?; + let block = input.parse::()?; + let exposed = Self::collect(&block.items).map_err(|e|input.error(e))?; + Ok(Self(block, exposed)) + } +} + +impl CommandImpl { + fn collect (items: &Vec) + -> std::result::Result, CommandArm>, String> + { let mut exposed: BTreeMap, CommandArm> = Default::default(); - for item in block.items.iter() { - if let ImplItem::Fn(ImplItemFn { - sig: Signature { ident, inputs, output, .. }, .. - }) = item { - let key: Arc = - format!("{}", AsKebabCase(format!("{}", &ident))).into(); - let variant: Arc = - format!("{}", AsUpperCamelCase(format!("{}", &ident))).into(); + for item in items.iter() { + if let ImplItem::Fn( + ImplItemFn { sig: Signature { ident, inputs, output, .. }, .. } + ) = item { + let key = CommandArm::ident_to_key(&ident); if exposed.contains_key(&key) { - return Err(input.error(format!("already defined: {ident}"))); + return Err(format!("already defined: {ident}")); } exposed.insert(key, CommandArm( - variant, ident.clone(), inputs.iter().map(|x|x.clone()).collect(), output.clone(), )); } } - Ok(Self(block, exposed)) + Ok(exposed) } } impl ToTokens for CommandDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(CommandMeta { target }, CommandImpl(block, exposed)) = self; - let enumeration = &block.self_ty; - let definitions = exposed.values().map(|x|CommandVariant( - x.1.clone(), - x.2.clone(), - )); - let implementations = exposed.values().map(|x|CommandArm( - x.0.clone(), - x.1.clone(), - x.2.clone(), - x.3.clone(), - )); - for token in quote! { - #block - enum #enumeration { - #(#definitions)* + let Self(CommandMeta(target), CommandImpl(block, exposed)) = self; + let enumeration = &block.self_ty; + let variants = exposed.values().map(|x|CommandArm::to_enum_variant(x, true, true, false)); + 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]. + pub enum #enumeration { + #(#variants)* } + #block + /// Generated by [tengri_proc]. + impl<'a> TryFromDsl<'a, #target> for #enumeration { + fn try_from_expr (state: &#target, iter: TokenIter) -> Option { + let mut iter = iter.clone(); + match iter.next() { + #(#matchers)* + _ => None + } + } + } + /// Generated by [tengri_proc]. impl Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { @@ -78,12 +84,107 @@ impl ToTokens for CommandDef { } } } - } { - out.append(token) + }); + if exposed.len() > 0 { + //panic!("\n{}", quote! {#out}); } } } +impl CommandArm { + fn to_key (&self) -> Arc { + Self::ident_to_key(&self.0) + } + fn to_enum_variant_ident (&self) -> Ident { + Ident::new(&Self::ident_to_enum_variant(&self.0), Span::call_site()) + } + fn ident_to_key (ident: &Ident) -> Arc { + format!("{}", AsKebabCase(format!("{ident}"))).into() + } + fn ident_to_enum_variant (ident: &Ident) -> Arc { + format!("{}", AsUpperCamelCase(format!("{ident}"))).into() + } + fn to_enum_variant (&self, with_types: bool, trailing_comma: bool, with_values: bool) -> TokenStream2 { + let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); + let ident = &self.0; + if self.1.len() > 2 { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for arg in self.1.iter().skip(2) { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + write_quote_to(&mut out, quote! { #pat }); + if with_types && with_values { + unreachable!(); + } + if with_types { + write_quote_to(&mut out, quote! { : #ty }); + } + if with_values { + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote_to(&mut out, quote! { + : Context::get(state, &iter.next().expect(#take_err).value) + }); + } + } else { + unreachable!("only typed args should be present at this position") + } + } + out + })); + } + if trailing_comma { + out.append(Punct::new(',', Alone)); + } + out + } + fn to_matcher (&self) -> TokenStream2 { + let mut out = TokenStream2::new(); + let key = LitStr::new(&self.to_key(), Span::call_site()); + let ident = &self.0; + let take_args = self.1.iter().skip(2).map(|arg|{ + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote(quote! { + let #pat: #ty = Context::<#ty>::get( + state, + &iter.next().expect(#take_err).value + ); + }) + } else { + unreachable!("only typed args should be present at this position") + } + }).collect::>(); + let variant = Self::ident_to_enum_variant(&self.0); + let variant = self.to_enum_variant(false, false, true); + write_quote(quote! { + Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { + let mut iter = iter.clone(); + //#(#take_args)* + //let rest = iter; // TODO + Some(Self::#variant) + }, + }) + } + fn to_implementation (&self) -> TokenStream2 { + let ident = &self.0; + let variant = self.to_enum_variant(false, false, false); + let mut give_rest = write_quote(quote! { }); + let give_args = self.1.iter().skip(2).map(|arg|{ + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", + //quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote(quote! { #pat, }) + } else { + unreachable!("only typed args should be present at this position") + } + }).collect::>(); + write_quote(quote! { Self::#variant => self.#ident(state, #(#give_args)* #give_rest), }) + } +} + impl ToTokens for CommandVariant { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ident, args) = self; @@ -92,9 +193,7 @@ impl ToTokens for CommandVariant { out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); for arg in args.iter() { - if let FnArg::Typed(PatType { - attrs, pat, colon_token, ty - }) = arg { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { out.append(LitStr::new( &format!("{}", quote! { #ty }), Span::call_site() @@ -110,34 +209,30 @@ impl ToTokens for CommandVariant { impl ToTokens for CommandArm { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(symbol, ident, args, returnType) = self; - 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())); + let Self(ident, args, returnType) = self; + for ident in ["tengri", "dsl", "Value", "Sym"].iter() { + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); + out.append(Ident::new(ident, Span::call_site())); + } out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); + out.append(self.to_enum_variant_ident()); for arg in args.iter() { - out.append(LitStr::new(&symbol, Span::call_site()).token()); } out })); out.append(Punct::new('=', Joint)); out.append(Punct::new('>', Alone)); - out.append(LitStr::new(&format!("{}", ident), Span::call_site()).token()); + out.append(Ident::new("self", Span::call_site())); + out.append(Punct::new('.', Alone)); + out.append(ident.clone()); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); for arg in args.iter() { // TODO - //out.append(LitStr::new(&symbol, Span::call_site()).token()); + out.append(LitStr::new(&self.to_key(), Span::call_site()).token()); + out.append(Punct::new(',', Alone)); } out })); From 046be9a9e1a88a9e453466abb092edfd422ed22b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 13:46:29 +0300 Subject: [PATCH 039/178] proc: working command, expose --- proc/src/proc_command.rs | 5 ++++- proc/src/proc_expose.rs | 32 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 8ad7776..2573d0f 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -62,6 +62,7 @@ impl ToTokens for CommandDef { let implementations = exposed.values().map(CommandArm::to_implementation); write_quote_to(out, quote! { /// Generated by [tengri_proc]. + #[derive(Clone, Debug)] pub enum #enumeration { #(#variants)* } @@ -123,9 +124,11 @@ impl CommandArm { if with_values { let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { : Context::get(state, &iter.next().expect(#take_err).value) - }); + .expect(#give_err) }); } } else { unreachable!("only typed args should be present at this position") diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 9315f2e..0303509 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -70,23 +70,31 @@ impl ToTokens for ExposeImpl { out.append(token); } for (t, variants) in exposed.iter() { - let predefined = match format!("{}", quote! { #t }).as_str() { - "bool" => vec![ - quote! { ::tengri::dsl::Value::Sym(":true") => true }, - quote! { ::tengri::dsl::Value::Sym(":false") => false }, - ], + let formatted_type = format!("{}", quote! { #t }); + let predefined = match formatted_type.as_str() { + "bool" => quote! { + ::tengri::dsl::Value::Sym(":true") => true, + ::tengri::dsl::Value::Sym(":false") => false, + }, "u8" | "u16" | "u32" | "u64" | "usize" | - "i8" | "i16" | "i32" | "i64" | "isize" => vec![ - quote! { ::tengri::dsl::Value::Num(n) => *n }, - ], - _ => vec![], + "i8" | "i16" | "i32" | "i64" | "isize" => { + let num_err = LitStr::new( + &format!("{{n}}: failed to convert to {formatted_type}"), + Span::call_site() + ); + quote! { + ::tengri::dsl::Value::Num(n) => TryInto::<#t>::try_into(*n) + .unwrap_or_else(|_|panic!(#num_err)), + } + }, + _ => quote! {}, }; let values = variants.iter().map(|(k, v)|ExposeArm(k.clone(), v.clone())); let trait_impl = quote! { impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { Some(match dsl { - #(#predefined,)* + #predefined #(#values,)* _ => return None }) @@ -97,9 +105,9 @@ impl ToTokens for ExposeImpl { out.append(token); } } - //if exposed.len() > 0 { + if exposed.len() > 0 { //panic!("{}", quote! {#out}); - //} + } } } From e3bfae889792e70fa88a02fcad90e531f41059ec Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 17:25:45 +0300 Subject: [PATCH 040/178] fix: command: commas --- proc/src/proc_command.rs | 82 ++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 2573d0f..3be238e 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -57,7 +57,7 @@ impl ToTokens for CommandDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(CommandMeta(target), CommandImpl(block, exposed)) = self; let enumeration = &block.self_ty; - let variants = exposed.values().map(|x|CommandArm::to_enum_variant(x, true, true, false)); + 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! { @@ -86,8 +86,12 @@ impl ToTokens for CommandDef { } } }); - if exposed.len() > 0 { - //panic!("\n{}", quote! {#out}); + //if exposed.len() > 0 { + //panic!("{:#?}", block.self_ty); + if let Type::Path(ref path) = *block.self_ty { + if path.path.segments.get(0).unwrap().ident == "TekCommand" { + //panic!("\n{}", quote! {#out}); + } } } } @@ -105,7 +109,7 @@ impl CommandArm { fn ident_to_enum_variant (ident: &Ident) -> Arc { format!("{}", AsUpperCamelCase(format!("{ident}"))).into() } - fn to_enum_variant (&self, with_types: bool, trailing_comma: bool, with_values: bool) -> TokenStream2 { + fn to_enum_variant_def (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); let ident = &self.0; @@ -114,22 +118,7 @@ impl CommandArm { let mut out = TokenStream2::new(); for arg in self.1.iter().skip(2) { if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { - write_quote_to(&mut out, quote! { #pat }); - if with_types && with_values { - unreachable!(); - } - if with_types { - write_quote_to(&mut out, quote! { : #ty }); - } - if with_values { - let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); - let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", - quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); - write_quote_to(&mut out, quote! { - : Context::get(state, &iter.next().expect(#take_err).value) - .expect(#give_err) }); - } + write_quote_to(&mut out, quote! { #pat : #ty , }); } else { unreachable!("only typed args should be present at this position") } @@ -137,8 +126,51 @@ impl CommandArm { out })); } - if trailing_comma { - out.append(Punct::new(',', Alone)); + 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.1.len() > 2 { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for arg in self.1.iter().skip(2) { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", + quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + write_quote_to(&mut out, quote! { + #pat : Context::get(state, &iter.next().expect(#take_err).value) + .expect(#give_err) , + }); + } else { + unreachable!("only typed args should be present at this position") + } + } + 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.1.len() > 2 { + out.append(Group::new(Delimiter::Brace, { + let mut out = TokenStream2::new(); + for arg in self.1.iter().skip(2) { + if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + write_quote_to(&mut out, quote! { #pat , }); + } else { + unreachable!("only typed args should be present at this position") + } + } + out + })); } out } @@ -161,7 +193,7 @@ impl CommandArm { } }).collect::>(); let variant = Self::ident_to_enum_variant(&self.0); - let variant = self.to_enum_variant(false, false, true); + let variant = self.to_enum_variant_bind(); write_quote(quote! { Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { let mut iter = iter.clone(); @@ -173,8 +205,8 @@ impl CommandArm { } fn to_implementation (&self) -> TokenStream2 { let ident = &self.0; - let variant = self.to_enum_variant(false, false, false); - let mut give_rest = write_quote(quote! { }); + let variant = self.to_enum_variant_unbind(); + let mut give_rest = write_quote(quote! { /*TODO*/ }); let give_args = self.1.iter().skip(2).map(|arg|{ if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", From 2a6087e1c7086f09b1ade22c84ff62642df7c723 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 17:39:02 +0300 Subject: [PATCH 041/178] fix: command: refs --- proc/src/lib.rs | 5 +++-- proc/src/proc_command.rs | 44 ++++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 82cb6c8..66dc0c7 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -1,5 +1,5 @@ #![feature(str_as_str)] - +#![feature(box_patterns)] extern crate proc_macro; pub(crate) use std::collections::{BTreeMap, BTreeSet}; @@ -14,7 +14,8 @@ pub(crate) use syn::{ parse, parse_macro_input, parse_quote as pq, braced, bracketed, parenthesized, Token, Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, - ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, PatType, + ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, + Pat, PatType, PatIdent, parse::{Parse, ParseStream, Result}, token::{PathSep, Brace}, punctuated::Punctuated, diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 3be238e..d3d2ec4 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -88,11 +88,11 @@ impl ToTokens for CommandDef { }); //if exposed.len() > 0 { //panic!("{:#?}", block.self_ty); - if let Type::Path(ref path) = *block.self_ty { - if path.path.segments.get(0).unwrap().ident == "TekCommand" { + //if let Type::Path(ref path) = *block.self_ty { + //if path.path.segments.get(0).unwrap().ident == "TekCommand" { //panic!("\n{}", quote! {#out}); - } - } + //} + //} } } @@ -117,8 +117,10 @@ impl CommandArm { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { - write_quote_to(&mut out, quote! { #pat : #ty , }); + if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident, .. }), .. + }) = arg { + write_quote_to(&mut out, quote! { #ident : #ty , }); } else { unreachable!("only typed args should be present at this position") } @@ -137,13 +139,15 @@ impl CommandArm { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. + }) = arg { let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", - quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { - #pat : Context::get(state, &iter.next().expect(#take_err).value) + #arg : Context::get(state, &iter.next().expect(#take_err).value) .expect(#give_err) , }); } else { @@ -163,8 +167,10 @@ impl CommandArm { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { - write_quote_to(&mut out, quote! { #pat , }); + if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. + }) = arg { + write_quote_to(&mut out, quote! { #arg , }); } else { unreachable!("only typed args should be present at this position") } @@ -179,11 +185,13 @@ impl CommandArm { let key = LitStr::new(&self.to_key(), Span::call_site()); let ident = &self.0; let take_args = self.1.iter().skip(2).map(|arg|{ - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. + }) = arg { let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); + quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote(quote! { - let #pat: #ty = Context::<#ty>::get( + let #ident: #ty = Context::<#ty>::get( state, &iter.next().expect(#take_err).value ); @@ -208,10 +216,12 @@ impl CommandArm { let variant = self.to_enum_variant_unbind(); let mut give_rest = write_quote(quote! { /*TODO*/ }); let give_args = self.1.iter().skip(2).map(|arg|{ - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. + }) = arg { //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", //quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); - write_quote(quote! { #pat, }) + write_quote(quote! { #arg, }) } else { unreachable!("only typed args should be present at this position") } From a16603fbc88a41c0cb4a315383cb647b0f9bb2b5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 18:37:42 +0300 Subject: [PATCH 042/178] proc: command: associated fns instead of methods --- proc/src/proc_command.rs | 113 ++++++++++++++------------------------- 1 file changed, 39 insertions(+), 74 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index d3d2ec4..9d1d9ad 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -109,21 +109,28 @@ impl CommandArm { fn ident_to_enum_variant (ident: &Ident) -> Arc { format!("{}", AsUpperCamelCase(format!("{ident}"))).into() } + fn has_args (&self) -> bool { + self.1.len() > 1 + } + fn args (&self) -> impl Iterator)> { + self.1.iter().skip(1).filter_map(|arg|if let FnArg::Typed(PatType { + ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. + }) = arg { + Some((arg, ty)) + } else { + unreachable!("only typed args should be present at this position"); + None + }) + } fn to_enum_variant_def (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); let ident = &self.0; - if self.1.len() > 2 { + if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); - for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { - ty, pat: box Pat::Ident(PatIdent { ident, .. }), .. - }) = arg { - write_quote_to(&mut out, quote! { #ident : #ty , }); - } else { - unreachable!("only typed args should be present at this position") - } + for (arg, ty) in self.args() { + write_quote_to(&mut out, quote! { #arg : #ty , }); } out })); @@ -135,24 +142,18 @@ impl CommandArm { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); let ident = &self.0; - if self.1.len() > 2 { + if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); - for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { - ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. - }) = arg { - let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); - let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", - quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); - write_quote_to(&mut out, quote! { - #arg : Context::get(state, &iter.next().expect(#take_err).value) - .expect(#give_err) , - }); - } else { - unreachable!("only typed args should be present at this position") - } + 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 = LitStr::new(&format!("{}: missing value \"{}\" ({})", + quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); + write_quote_to(&mut out, quote! { + #arg : Context::get(state, &iter.next().expect(#take_err).value) + .expect(#give_err) , + }); } out })); @@ -163,17 +164,11 @@ impl CommandArm { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); let ident = &self.0; - if self.1.len() > 2 { + if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); - for arg in self.1.iter().skip(2) { - if let FnArg::Typed(PatType { - ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. - }) = arg { - write_quote_to(&mut out, quote! { #arg , }); - } else { - unreachable!("only typed args should be present at this position") - } + for (arg, ty) in self.args() { + write_quote_to(&mut out, quote! { #arg , }); } out })); @@ -181,52 +176,21 @@ impl CommandArm { out } fn to_matcher (&self) -> TokenStream2 { - let mut out = TokenStream2::new(); - let key = LitStr::new(&self.to_key(), Span::call_site()); - let ident = &self.0; - let take_args = self.1.iter().skip(2).map(|arg|{ - if let FnArg::Typed(PatType { - ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. - }) = arg { - let take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", - quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); - write_quote(quote! { - let #ident: #ty = Context::<#ty>::get( - state, - &iter.next().expect(#take_err).value - ); - }) - } else { - unreachable!("only typed args should be present at this position") - } - }).collect::>(); - let variant = Self::ident_to_enum_variant(&self.0); + 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! { - Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Key(#key), .. }) => { - let mut iter = iter.clone(); - //#(#take_args)* - //let rest = iter; // TODO - Some(Self::#variant) - }, + #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 mut give_rest = write_quote(quote! { /*TODO*/ }); - let give_args = self.1.iter().skip(2).map(|arg|{ - if let FnArg::Typed(PatType { - ty, pat: box Pat::Ident(PatIdent { ident: arg, .. }), .. - }) = arg { - //let give_err = LitStr::new(&format!("{}: missing value \"{}\" ({})", - //quote!{#ident}, quote!{#pat}, quote!{#ty}), Span::call_site()); - write_quote(quote! { #arg, }) - } else { - unreachable!("only typed args should be present at this position") - } - }).collect::>(); - write_quote(quote! { Self::#variant => self.#ident(state, #(#give_args)* #give_rest), }) + 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), }) } } @@ -269,8 +233,9 @@ impl ToTokens for CommandArm { })); out.append(Punct::new('=', Joint)); out.append(Punct::new('>', Alone)); - out.append(Ident::new("self", Span::call_site())); - out.append(Punct::new('.', Alone)); + out.append(Ident::new("Self", Span::call_site())); + out.append(Punct::new(':', Joint)); + out.append(Punct::new(':', Alone)); out.append(ident.clone()); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); From bcbcc387a27f424b2199a3cdf16b78b0653b6592 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 20:18:07 +0300 Subject: [PATCH 043/178] proc: cleanup --- proc/src/proc_command.rs | 2 +- proc/src/proc_expose.rs | 65 +++++++++++++--------------------------- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 9d1d9ad..7102fd8 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -202,7 +202,7 @@ impl ToTokens for CommandVariant { out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); for arg in args.iter() { - if let FnArg::Typed(PatType { attrs, pat, colon_token, ty }) = arg { + if let FnArg::Typed(PatType { ty, .. }) = arg { out.append(LitStr::new( &format!("{}", quote! { #ty }), Span::call_site() diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 0303509..f3bb235 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -7,10 +7,7 @@ pub(crate) struct ExposeDef(pub(crate) ExposeMeta, pub(crate) ExposeImpl); pub(crate) struct ExposeMeta; #[derive(Debug, Clone)] -pub(crate) struct ExposeImpl { - block: ItemImpl, - exposed: BTreeMap>, -} +pub(crate) struct ExposeImpl(ItemImpl, BTreeMap>); #[derive(Debug, Clone)] struct ExposeArm(String, Ident); @@ -49,26 +46,22 @@ impl Parse for ExposeImpl { } } } - Ok(Self { block, exposed }) + Ok(Self(block, exposed)) } } impl ToTokens for ExposeDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(meta, data) = self; - for token in quote! { #data } { - out.append(token) - } + let Self(_meta, data) = self; + write_quote_to(out, quote! { #data }); } } impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { - let Self { block, exposed } = self; - let target = &self.block.self_ty; - for token in quote! { #block } { - out.append(token); - } + let Self(block, exposed) = self; + let target = &block.self_ty; + write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { let formatted_type = format!("{}", quote! { #t }); let predefined = match formatted_type.as_str() { @@ -89,8 +82,8 @@ impl ToTokens for ExposeImpl { }, _ => quote! {}, }; - let values = variants.iter().map(|(k, v)|ExposeArm(k.clone(), v.clone())); - let trait_impl = quote! { + let values = variants.iter().map(ExposeArm::from); + write_quote_to(out, quote! { impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { Some(match dsl { @@ -100,10 +93,7 @@ impl ToTokens for ExposeImpl { }) } } - }; - for token in trait_impl { - out.append(token); - } + }); } if exposed.len() > 0 { //panic!("{}", quote! {#out}); @@ -111,34 +101,19 @@ impl ToTokens for ExposeImpl { } } +impl From<(&String, &Ident)> for ExposeArm { + fn from ((a, b): (&String, &Ident)) -> Self { + Self(a.clone(), b.clone()) + } +} + impl ToTokens for ExposeArm { fn to_tokens (&self, out: &mut TokenStream2) { let Self(key, value) = self; - 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('=', Joint)); - out.append(Punct::new('>', Alone)); - out.append(Ident::new("self", Span::call_site())); - out.append(Punct::new('.', Alone)); - for token in quote! { #value } { - out.append(token); - } - out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); + let key = LitStr::new(&key, Span::call_site()); + write_quote_to(out, quote! { + ::tengri::dsl::Value::Sym(#key) => self.#value() + }) } } From b7bb6119aac975632969719c7ec5b71d97dbe356 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 8 May 2025 22:07:10 +0300 Subject: [PATCH 044/178] remove old declarative macros --- Cargo.lock | 1 + dsl/src/dsl_macros.rs | 121 ----------------------------------- dsl/src/lib.rs | 22 ++++--- input/src/input_macros.rs | 119 ---------------------------------- proc/src/proc_command.rs | 2 - proc/src/proc_expose.rs | 1 + tui/Cargo.toml | 5 +- tui/examples/demo.rs.old | 131 -------------------------------------- tui/examples/tui.rs | 39 +++++------- 9 files changed, 33 insertions(+), 408 deletions(-) delete mode 100644 tui/examples/demo.rs.old diff --git a/Cargo.lock b/Cargo.lock index 0df7fbe..d9ccc29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,6 +998,7 @@ dependencies = [ "tengri_dsl", "tengri_input", "tengri_output", + "tengri_proc", "unicode-width 0.2.0", ] diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs index 338583f..93b2cea 100644 --- a/dsl/src/dsl_macros.rs +++ b/dsl/src/dsl_macros.rs @@ -24,127 +24,6 @@ } } -/// Implement `Context` for one or more base structs, types, and keys. */ -#[macro_export] macro_rules! expose { - ($([$self:ident:$State:ty] $(([$($Type:tt)*] $(($pat:literal $expr:expr))*))*)*) => { - $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* - }; - ($([$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* })*) => { - $(expose!(@impl [$self: $State] { $([$($Type)*] => { $($pat => $expr),* })* });)* - }; - (@impl [$self:ident:$State:ty] { $([$($Type:tt)*] => { $($pat:pat => $expr:expr),* $(,)? })* }) => { - $(expose!(@type [$($Type)*] [$self: $State] => { $($pat => $expr),* });)* - }; - (@type [bool] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_bool!(bool: |$self: $State| { $($pat => $expr),* }); - }; - (@type [u16] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(u16: |$self: $State| { $($pat => $expr),* }); - }; - (@type [usize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(usize: |$self: $State| { $($pat => $expr),* }); - }; - (@type [isize] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide_num!(isize: |$self: $State| { $($pat => $expr),* }); - }; - (@type [$Type:ty] [$self:ident: $State:ty] => { $($pat:pat => $expr:expr),* $(,)? }) => { - provide!($Type: |$self: $State| { $($pat => $expr),* }); - }; -} - -/// Implement `Context` for a context and type. -#[macro_export] macro_rules! provide { - // Provide a value to the EDN template - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - #[allow(unreachable_code)] - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) - } - } - }; - // Provide a value more generically - ($lt:lifetime: $type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$lt> Context<$lt, $type> for $State { - #[allow(unreachable_code)] - fn get (&$lt $self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* _ => return None }) - } - } - }; -} - -/// Implement `Context` for a context and numeric type. -/// -/// This enables support for numeric literals. -#[macro_export] macro_rules! provide_num { - // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. - ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) - } - } - }; - // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { $(Sym($pat) => $expr,)* Num(n) => *n as $type, _ => return None }) - } - } - }; -} - -/// Implement `Context` for a context and the boolean type. -/// -/// This enables support for boolean literals. -#[macro_export] macro_rules! provide_bool { - // Provide a value that may also be a numeric literal in the EDN, to a generic implementation. - ($type:ty:|$self:ident:<$T:ident:$Trait:path>|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl<$T: $Trait> Context<$type> for $T { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { - Num(n) => match *n { 0 => false, _ => true }, - Sym(":false") | Sym(":f") => false, - Sym(":true") | Sym(":t") => true, - $(Sym($pat) => $expr,)* - _ => return Context::get(self, dsl) - }) - } - } - }; - // Provide a value that may also be a numeric literal in the EDN, to a concrete implementation. - ($type:ty:|$self:ident:$State:ty|{ $($pat:pat => $expr:expr),* $(,)? }) => { - impl Context<$type> for $State { - fn get (&$self, dsl: &Value) -> Option<$type> { - use Value::*; - Some(match dsl { - Num(n) => match *n { 0 => false, _ => true }, - Sym(":false") | Sym(":f") => false, - Sym(":true") | Sym(":t") => true, - $(Sym($pat) => $expr,)* - _ => return None - }) - } - } - }; -} - -#[macro_export] macro_rules! impose { - ([$self:ident:$Struct:ty] $(($Command:ty : $(($cmd:literal $args:tt $result:expr))*))*) => { - $(atom_command!($Command: |$self: $Struct| { $(($cmd $args $result))* });)* - }; - ([$self:ident:$Struct:ty] { $($Command:ty => $variants:tt)* }) => { - $(atom_command!($Command: |$self: $Struct| $variants);)* - }; -} - #[macro_export] macro_rules! get_value { ($state:expr => $token:expr) => { if let Some(value) = $state.get(&$token.value) { diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 994fc80..411974a 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -116,16 +116,18 @@ mod dsl_macros; #[cfg(test)] #[test] fn test_dsl_context () { struct Test; - provide_bool!(bool: |self: Test|{ - ":provide-bool" => true - }); - let test = Test; - assert_eq!(test.get(&Value::Sym(":false")), Some(false)); - assert_eq!(test.get(&Value::Sym(":true")), Some(true)); - assert_eq!(test.get(&Value::Sym(":provide-bool")), Some(true)); - assert_eq!(test.get(&Value::Sym(":missing-bool")), None); - assert_eq!(test.get(&Value::Num(0)), Some(false)); - assert_eq!(test.get(&Value::Num(1)), Some(true)); + #[tengri_proc::expose] + impl Test { + fn some_bool (&self) -> bool { + true + } + } + assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); + assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); + assert_eq!(Test.get(&Value::Num(0)), Some(false)); + assert_eq!(Test.get(&Value::Num(1)), Some(true)); } //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { diff --git a/input/src/input_macros.rs b/input/src/input_macros.rs index c3e13e9..689c94e 100644 --- a/input/src/input_macros.rs +++ b/input/src/input_macros.rs @@ -1,30 +1,5 @@ use crate::*; -/** Implement `Command` for given `State` and collection - * of `Variant` to `handler` mappings. */ -#[macro_export] macro_rules! defcom { - ([$self:ident, $state:ident:$State:ty] $(($Command:ident $(( - $Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr - ))*))*) => { - $(#[derive(Clone, Debug)] pub enum $Command { - $($Variant $(($($Param),+))?),* - })* - $(command!(|$self: $Command, $state: $State|match $self { - $($Command::$Variant $(($($param),+))? => $expr),* - });)* - }; - (|$self:ident, $state:ident:$State:ty| $($Command:ident { $( - $Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr - )* $(,)? })*) => { - $(#[derive(Clone, Debug)] pub enum $Command { - $($Variant $(($($Param),+))?),* - })* - $(command!(|$self: $Command, $state: $State|match $self { - $($Command::$Variant $(($($param),+))? => $expr),* - });)* - }; -} - /** Implement `Command` for given `State` and `handler` */ #[macro_export] macro_rules! command { ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { @@ -35,97 +10,3 @@ use crate::*; } }; } - -/** Implement `DslCommand` for given `State` and `Command` */ -#[cfg(feature = "dsl")] -#[macro_export] macro_rules! atom_command { - ($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $(( - // identifier - $key:literal [ - // named parameters - $( - // argument name - $arg:ident - // if type is not provided defaults to Dsl - $( - // type:name separator - : - // argument type - $type:ty - )? - ),* - // rest of parameters - $(, ..$rest:ident)? - ] - // bound command: - $command:expr - ))* }) => { - impl<'a, $State: $Trait> TryFromDsl<'a, $State> for $Command { - fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { - let iter = iter.clone(); - match iter.next() { - $(Some(Token { value: Value::Key($key), .. }) => { - let iter = iter.clone(); - $( - let next = iter.next(); - if next.is_none() { panic!("no argument: {}", stringify!($arg)); } - let $arg = next.unwrap(); - $(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)? - )* - $(let $rest = iter.clone();)? - return $command - },)* - _ => None - } - None - } - } - }; - ($Command:ty : |$state:ident:$State:ty| { $(( - // identifier - $key:literal [ - // named parameters - $( - // argument name - $arg:ident - // if type is not provided defaults to Dsl - $( - // type:name separator - : - // argument type - $type:ty - )? - ),* - // rest of parameters - $(, ..$rest:ident)? - ] - // bound command: - $command:expr - ))* }) => { - impl<'a> TryFromDsl<'a, $State> for $Command { - fn try_from_expr ($state: &$State, iter: TokenIter) -> Option { - 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); - }; -} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 7102fd8..b9ececc 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -227,8 +227,6 @@ impl ToTokens for CommandArm { out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); - for arg in args.iter() { - } out })); out.append(Punct::new('=', Joint)); diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index f3bb235..affb192 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -84,6 +84,7 @@ impl ToTokens for ExposeImpl { }; let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { + /// Generated by [tengri_proc]. impl ::tengri::dsl::Context<#t> for #target { fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { Some(match dsl { diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 5bf0dc2..a9eb4d9 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -20,8 +20,9 @@ tengri_output = { path = "../output" } tengri_dsl = { optional = true, path = "../dsl" } [dev-dependencies] -tengri = { path = "../tengri", features = [ "dsl" ] } -tengri_dsl = { path = "../dsl" } +tengri = { path = "../tengri", features = [ "dsl" ] } +tengri_dsl = { path = "../dsl" } +tengri_proc = { path = "../proc" } [features] dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ] diff --git a/tui/examples/demo.rs.old b/tui/examples/demo.rs.old deleted file mode 100644 index ba013de..0000000 --- a/tui/examples/demo.rs.old +++ /dev/null @@ -1,131 +0,0 @@ -use tek::*; - -fn main () -> Usually<()> { - Tui::run(Arc::new(RwLock::new(Demo::new())))?; - Ok(()) -} - -pub struct Demo { - index: usize, - items: Vec>> -} - -impl Demo { - fn new () -> Self { - Self { - index: 0, - items: vec![] - } - } -} - -impl Content for Demo { - type Engine = Tui; - fn content (&self) -> dyn Render { - let border_style = Style::default().fg(Color::Rgb(0,0,0)); - Align::Center(Layers::new(move|add|{ - - add(&Background(Color::Rgb(0,128,128)))?; - - add(&Margin::XY(1, 1, Stack::down(|add|{ - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(128,96,0)))?; - add(&Border(Square(border_style)))?; - add(&Margin::XY(2, 1, "..."))?; - Ok(()) - }).debug())?; - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(128,64,0)))?; - add(&Border(Lozenge(border_style)))?; - add(&Margin::XY(4, 2, "---"))?; - Ok(()) - }).debug())?; - - add(&Layers::new(|add|{ - add(&Background(Color::Rgb(96,64,0)))?; - add(&Border(SquareBold(border_style)))?; - add(&Margin::XY(6, 3, "~~~"))?; - Ok(()) - }).debug())?; - - Ok(()) - })).debug())?; - - Ok(()) - - })) - //Align::Center(Margin::X(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(128,0,0)))?; - //add(&Stack::down(|add|{ - //add(&Margin::Y(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(0,128,0)))?; - //add(&Align::Center("12345"))?; - //add(&Align::Center("FOO")) - //})))?; - //add(&Margin::XY(1, 1, Layers::new(|add|{ - //add(&Align::Center("1234567"))?; - //add(&Align::Center("BAR"))?; - //add(&Background(Color::Rgb(0,0,128))) - //}))) - //})) - //}))) - - //Align::Y(Layers::new(|add|{ - //add(&Background(Color::Rgb(128,0,0)))?; - //add(&Margin::X(1, Align::Center(Stack::down(|add|{ - //add(&Align::X(Margin::Y(1, Layers::new(|add|{ - //add(&Background(Color::Rgb(0,128,0)))?; - //add(&Align::Center("12345"))?; - //add(&Align::Center("FOO")) - //})))?; - //add(&Margin::XY(1, 1, Layers::new(|add|{ - //add(&Align::Center("1234567"))?; - //add(&Align::Center("BAR"))?; - //add(&Background(Color::Rgb(0,0,128))) - //})))?; - //Ok(()) - //}))))) - //})) - } -} - -impl Handle for Demo { - fn handle (&mut self, from: &TuiIn) -> Perhaps { - use KeyCode::{PageUp, PageDown}; - match from.event() { - kexp!(PageUp) => { - self.index = (self.index + 1) % self.items.len(); - }, - kexp!(PageDown) => { - self.index = if self.index > 1 { - self.index - 1 - } else { - self.items.len() - 1 - }; - }, - _ => return Ok(None) - } - Ok(Some(true)) - } -} - -//lisp!(CONTENT Demo (LET - //(BORDER-STYLE (STYLE (FG (RGB 0 0 0)))) - //(BG-COLOR-0 (RGB 0 128 128)) - //(BG-COLOR-1 (RGB 128 96 0)) - //(BG-COLOR-2 (RGB 128 64 0)) - //(BG-COLOR-3 (RGB 96 64 0)) - //(CENTER (LAYERS - //(BACKGROUND BG-COLOR-0) - //(OUTSET-XY 1 1 (SPLIT-DOWN - //(LAYERS (BACKGROUND BG-COLOR-1) - //(BORDER SQUARE BORDER-STYLE) - //(OUTSET-XY 2 1 "...")) - //(LAYERS (BACKGROUND BG-COLOR-2) - //(BORDER LOZENGE BORDER-STYLE) - //(OUTSET-XY 4 2 "---")) - //(LAYERS (BACKGROUND BG-COLOR-3) - //(BORDER SQUARE-BOLD BORDER-STYLE) - //(OUTSET-XY 2 1 "~~~")))))))) diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 7d9f9d7..3869a8e 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -41,23 +41,24 @@ handle!(TuiIn: |self: Example, input|{ }) }); -defcom! { |self, state: Example| - ExampleCommand { - Next => { - state.0 = (state.0 + 1) % EXAMPLES.len(); - None - } - Prev => { - state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 }; - None - } - } +#[tengri_proc::expose] +impl Example { + //[bool] => {} + //[u16] => {} + //[usize] => {} } -atom_command!(ExampleCommand: |app: Example| { - ("prev" [] Some(Self::Prev)) - ("next" [] Some(Self::Next)) -}); +#[tengri_proc::command(Example)] +impl ExampleCommand { + fn next (state: &mut Example) -> Perhaps { + state.0 = (state.0 + 1) % EXAMPLES.len(); + Ok(None) + } + fn prev (state: &mut Example) -> Perhaps { + state.0 = if state.0 > 0 { state.0 - 1 } else { EXAMPLES.len() - 1 }; + Ok(None) + } +} view!(TuiOut: |self: Example|{ let index = self.0 + 1; @@ -77,11 +78,3 @@ view!(TuiOut: |self: Example|{ ":map-e" => Map::east(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), ":map-s" => Map::south(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), }); - -expose! { - [self: Example] { - [bool] => {} - [u16] => {} - [usize] => {} - } -} From 22d63eed9c9cb5bed5016d851f90773e0f60280d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 01:38:18 +0300 Subject: [PATCH 045/178] input, dsl: cleanup --- dsl/src/dsl_iter.rs | 2 +- input/src/_input.rs | 36 ----------------- input/src/_input_event_map.rs | 73 ----------------------------------- input/src/input_macros.rs | 2 - 4 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 input/src/_input.rs delete mode 100644 input/src/_input_event_map.rs diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index 269bc55..acdbe4b 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -105,7 +105,7 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { }), _ => token.error(Unexpected(c)) }, - Str(s) => match c { + Str(_) => match c { '"' => return Some(token), _ => token.grow_str(), }, diff --git a/input/src/_input.rs b/input/src/_input.rs deleted file mode 100644 index 24dd927..0000000 --- a/input/src/_input.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::*; -use std::time::Duration; -use std::thread::JoinHandle; - -/// 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> 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); -} - -/// Input thread entrypoint. -pub trait InputRun { - fn run_input (engine: T, state: Self, timer: Duration) -> JoinHandle<()>; -} - -/// Handle input through a mutable reference. -pub trait Handle: Send + Sync { - fn handle (&mut self, _input: &E) -> Perhaps { - Ok(None) - } -} - -/// Handle input through an immutable reference (e.g. [Arc] or [Arc]) -pub trait HandleRef: Send + Sync { - fn handle (&self, _input: &E) -> Perhaps { - Ok(None) - } -} diff --git a/input/src/_input_event_map.rs b/input/src/_input_event_map.rs deleted file mode 100644 index fc9644d..0000000 --- a/input/src/_input_event_map.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::*; - -pub struct EventMap<'a, S, I: PartialEq, C> { - pub bindings: &'a [(I, &'a dyn Fn(&S) -> Option)], - pub fallback: Option<&'a dyn Fn(&S, &I) -> Option> -} - -impl<'a, S, I: PartialEq, C> EventMap<'a, S, I, C> { - pub fn handle (&self, state: &S, input: &I) -> Option { - 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 { - 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 { - Some($handler) - } - } - } -} - -pub trait InputToCommand: Command + Sized { - fn input_to_command (state: &S, input: &I) -> Option; - fn execute_with_state (state: &mut S, input: &I) -> Perhaps { - Ok(if let Some(command) = Self::input_to_command(state, input) { - let _undo = command.execute(state)?; - Some(true) - } else { - None - }) - } -} diff --git a/input/src/input_macros.rs b/input/src/input_macros.rs index 689c94e..ae5758c 100644 --- a/input/src/input_macros.rs +++ b/input/src/input_macros.rs @@ -1,5 +1,3 @@ -use crate::*; - /** Implement `Command` for given `State` and `handler` */ #[macro_export] macro_rules! command { ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { From 5e09f5a4bb386ab9cb3a80f4e74ac521a817a4e8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 18:17:10 +0300 Subject: [PATCH 046/178] wip: dsl, input, output, proc: more precise lifetimes --- dsl/src/dsl_context.rs | 43 ++++++++++++------------- dsl/src/dsl_token.rs | 26 ++++++++-------- input/src/input_dsl.rs | 58 +++++++++++++++++----------------- output/src/ops/align.rs | 2 +- output/src/ops/bsp.rs | 2 +- output/src/ops/cond.rs | 8 ++--- output/src/ops/transform.rs | 28 +++++++++-------- output/src/view.rs | 62 +++++++++++++++++++++++-------------- proc/src/proc_command.rs | 11 ++++--- proc/src/proc_expose.rs | 16 +++++----- proc/src/proc_view.rs | 6 ++-- 11 files changed, 140 insertions(+), 122 deletions(-) diff --git a/dsl/src/dsl_context.rs b/dsl/src/dsl_context.rs index b6daf5f..4acbf63 100644 --- a/dsl/src/dsl_context.rs +++ b/dsl/src/dsl_context.rs @@ -1,11 +1,17 @@ use crate::*; -pub trait TryFromDsl<'a, T>: Sized { - fn try_from_expr (_state: &'a T, _iter: TokenIter<'a>) -> Option { +pub trait TryFromDsl<'state, T>: Sized { + fn try_from_expr <'source: 'state> ( + _state: &'state T, _iter: TokenIter<'source> + ) -> Option { None } - fn try_from_atom (state: &'a T, value: Value<'a>) -> Option { - if let Exp(0, iter) = value { return Self::try_from_expr(state, iter) } + fn try_from_atom <'source: 'state> ( + state: &'state T, value: Value<'source> + ) -> Option { + if let Exp(0, iter) = value { + return Self::try_from_expr(state, iter.clone()) + } None } } @@ -15,29 +21,20 @@ pub trait TryIntoDsl: Sized { } /// Map EDN tokens to parameters of a given type for a given context -pub trait Context: Sized { - fn get (&self, _atom: &Value) -> Option { +pub trait Context<'state, U>: Sized { + fn get <'source> (&'state self, _iter: &mut TokenIter<'source>) -> Option { None } - fn get_or_fail (&self, dsl: &Value) -> U { - self.get(dsl).expect("no value") +} + +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, U> Context for &T { - fn get (&self, dsl: &Value) -> Option { - (*self).get(dsl) - } - fn get_or_fail (&self, dsl: &Value) -> U { - (*self).get_or_fail(dsl) - } -} - -impl, U> Context for Option { - fn get (&self, dsl: &Value) -> Option { - self.as_ref().map(|s|s.get(dsl)).flatten() - } - fn get_or_fail (&self, dsl: &Value) -> U { - self.as_ref().map(|s|s.get_or_fail(dsl)).expect("no provider") +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() } } diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs index d58a60e..e26ee93 100644 --- a/dsl/src/dsl_token.rs +++ b/dsl/src/dsl_token.rs @@ -22,38 +22,38 @@ //!``` use crate::*; -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'a> { - pub source: &'a str, +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { + pub source: &'source str, pub start: usize, pub length: usize, - pub value: Value<'a>, + pub value: Value<'source>, } -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'a> { +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { #[default] Nil, Err(ParseError), Num(usize), - Sym(&'a str), - Key(&'a str), - Str(&'a str), - Exp(usize, TokenIter<'a>), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, TokenIter<'source>), } -impl<'a> Token<'a> { - pub const fn new (source: &'a str, start: usize, length: usize, value: Value<'a>) -> Self { +impl<'source> Token<'source> { + pub const fn new (source: &'source str, start: usize, length: usize, value: Value<'source>) -> Self { Self { source, start, length, value } } pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } - pub const fn slice (&'a self) -> &'a str { + pub const fn slice (&'source self) -> &'source str { self.slice_source(self.source) //str_range(self.source, self.start, self.end()) } - pub const fn slice_source <'b> (&'a self, source: &'b str) -> &'b str { + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { str_range(source, self.start, self.end()) } - pub const fn slice_source_exp <'b> (&'a self, source: &'b str) -> &'b str { + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { str_range(source, self.start.saturating_add(1), self.end()) } pub const fn value (&self) -> Value { diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 49f77fc..bb5346d 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -2,9 +2,9 @@ use crate::*; use std::marker::PhantomData; /// A [Command] that can be constructed from a [Token]. -pub trait DslCommand<'a, C>: TryFromDsl<'a, C> + Command {} +pub trait DslCommand<'state, C>: TryFromDsl<'state, C> + Command {} -impl<'a, C, T: TryFromDsl<'a, C> + Command> DslCommand<'a, C> for T {} +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 { @@ -12,14 +12,14 @@ pub trait DslInput: Input { } /// A pre-configured mapping of input events to commands. -pub trait KeyMap<'a, S, C: DslCommand<'a, S>, 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 command (&'a self, state: &'a S, input: &'a I) -> Option; + fn command (&'state self, state: &'state S, input: &'state I) -> Option; } /// A [SourceIter] can be a [KeyMap]. -impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for SourceIter<'a> { - fn command (&'a self, state: &'a S, input: &'a I) -> Option { +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; @@ -45,8 +45,8 @@ impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for SourceIte } /// A [TokenIter] can be a [KeyMap]. -impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter<'a> { - fn command (&'a self, state: &'a S, input: &'a I) -> Option { +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 { @@ -70,25 +70,25 @@ impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter } } -pub type InputLayerCond<'a, S> = Boxbool + Send + Sync + 'a>; +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<'a, S, C, I, M> +pub struct InputMap<'state, S, C, I, M> where - C: DslCommand<'a, S>, + C: DslCommand<'state, S>, I: DslInput, - M: KeyMap<'a, S, C, I> + Send + Sync + M: KeyMap<'state, S, C, I> + Send + Sync { - __: &'a PhantomData<(S, C, I)>, - pub layers: Vec<(InputLayerCond<'a, S>, M)>, + __: &'state PhantomData<(S, C, I)>, + pub layers: Vec<(InputLayerCond<'state, S>, M)>, } -impl<'a, S, C, I, M> Default for InputMap<'a, S, C, I, M> +impl<'state, S, C, I, M> Default for InputMap<'state, S, C, I, M> where - C: DslCommand<'a, S>, + C: DslCommand<'state, S>, I: DslInput, - M: KeyMap<'a, S, C, I> + Send + Sync + M: KeyMap<'state, S, C, I> + Send + Sync { fn default () -> Self { Self { @@ -98,11 +98,11 @@ where } } -impl<'a, S, C, I, M> InputMap<'a, S, C, I, M> +impl<'state, S, C, I, M> InputMap<'state, S, C, I, M> where - C: DslCommand<'a, S>, + C: DslCommand<'state, S>, I: DslInput, - M: KeyMap<'a, S, C, I> + Send + Sync + M: KeyMap<'state, S, C, I> + Send + Sync { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) @@ -115,21 +115,21 @@ where self.add_layer_if(Box::new(|_|true), keymap); self } - pub fn layer_if (mut self, condition: InputLayerCond<'a, S>, 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<'a, S>, 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<'a, S, C, I, M> std::fmt::Debug for InputMap<'a, S, C, I, M> +impl<'state, S, C, I, M> std::fmt::Debug for InputMap<'state, S, C, I, M> where - C: DslCommand<'a, S>, + C: DslCommand<'state, S>, I: DslInput, - M: KeyMap<'a, S, C, I> + Send + Sync + 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()); @@ -138,13 +138,13 @@ where } /// An [InputMap] can be a [KeyMap]. -impl<'a, S, C, I, M> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I, M> +impl<'state, S, C, I, M> KeyMap<'state, S, C, I> for InputMap<'state, S, C, I, M> where - C: DslCommand<'a, S>, + C: DslCommand<'state, S>, I: DslInput, - M: KeyMap<'a, S, C, I> + Send + Sync + M: KeyMap<'state, S, C, I> + Send + Sync { - fn command (&'a self, state: &'a S, input: &'a I) -> Option { + fn command (&'state self, state: &'state S, input: &'state I) -> Option { for (condition, keymap) in self.layers.iter() { if !condition(state) { continue diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 35a2b68..d4c7fb3 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,7 +36,7 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -try_from_expr!(<'a, E>: Align>: |state, iter|{ +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"| diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 5ede569..0ab80c3 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -17,7 +17,7 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -try_from_expr!(<'a, E>: Bsp, RenderBox<'a, E>>: |state, iter| { +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" => { diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index f92f157..521fdf8 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -19,12 +19,12 @@ impl Either { } #[cfg(feature = "dsl")] -try_from_expr!(<'a, E>: When>: |state, iter| { +try_from_expr!(<'source, 'state, E>: When>: |state, iter| { if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { let _ = iter.next().unwrap(); let condition = iter.next().expect("no condition specified"); - let condition = state.get(&condition.value).expect("no condition provided"); + let condition = state.get(&mut iter).expect("no condition provided"); let content = iter.next().expect("no content specified"); let content = if let Some(content) = state.get_content(&content.value) { @@ -38,12 +38,12 @@ try_from_expr!(<'a, E>: When>: |state, iter| { }); #[cfg(feature = "dsl")] -try_from_expr!(<'a, E>: Either, RenderBox<'a, E>>: |state, iter| { +try_from_expr!(<'source, 'state, E>: Either, RenderBox<'state, E>>: |state, iter| { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { let _ = iter.next().unwrap(); let condition = iter.next().expect("no condition specified"); - let condition = state.get(&condition.value).expect("no condition provided"); + let condition = state.get(&mut iter).expect("no condition provided"); let content = iter.next().expect("no content specified"); let content = if let Some(content) = state.get_content(&content.value) { diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 6012a96..0dd2264 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,9 +33,11 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: T) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T> - for $Enum> { - fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { + impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> + for $Enum> { + fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) + -> Option + { let mut iter = iter.clone(); if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { if k == $x || k == $y || k == $xy { @@ -80,17 +82,16 @@ 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<'a, E: Output + 'a, T: ViewContext<'a, E>> TryFromDsl<'a, T> - for $Enum> { - fn try_from_expr (state: &'a T, iter: TokenIter<'a>) -> Option { + impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> + for $Enum> { + fn try_from_expr <'source: 'state> (state: &'state T, iter: 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 = iter.next().expect("no unit specified"); - let u = get_value!(state => u); - let c = iter.next().expect("no content specified"); - let c = get_content!(state => c); + 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), @@ -98,9 +99,10 @@ macro_rules! transform_xy_unit { }) } else if k == $xy { let _ = iter.next().unwrap(); - let u = get_value!(state => iter.next().expect("no unit specified")); - let v = get_value!(state => iter.next().expect("no unit specified")); - let c = get_content!(state => iter.next().expect("no content specified")); + 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)) } } diff --git a/output/src/view.rs b/output/src/view.rs index c18713f..4bf3d73 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -45,34 +45,37 @@ impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content for View<'a, T> { // Provides components to the view. #[cfg(feature = "dsl")] -pub trait ViewContext<'a, E: Output + 'a>: Send + Sync - + Context - + Context - + Context +pub trait ViewContext<'state, E: Output + 'state>: Send + Sync + + Context<'state, bool> + + Context<'state, usize> + + Context<'state, E::Unit> { - fn get_content (&'a self, value: &Value<'a>) -> Option> { + 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") } } - fn get_content_sym (&'a self, value: &Value<'a>) -> Option>; - fn get_content_exp (&'a self, value: &Value<'a>) -> Option> { - try_delegate!(self, *value, When::>); - try_delegate!(self, *value, Either::, RenderBox<'a, E>>); - try_delegate!(self, *value, Align::>); - try_delegate!(self, *value, Bsp::, RenderBox<'a, E>>); - try_delegate!(self, *value, Fill::>); - try_delegate!(self, *value, Fixed::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Min::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Max::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Shrink::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Expand::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Push::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Pull::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Margin::<_, RenderBox<'a, E>>); - try_delegate!(self, *value, Padding::<_, RenderBox<'a, E>>); + fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) + -> Option>; + fn get_content_exp <'source: 'state> (&'state self, value: &Value<'source>) + -> Option> + { + 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 } } @@ -88,9 +91,20 @@ pub trait ViewContext<'a, E: Output + 'a>: Send + Sync #[cfg(feature = "dsl")] #[macro_export] macro_rules! try_from_expr { - (<$l:lifetime, $E:ident>: $Struct:ty: |$state:ident, $iter:ident|$body:expr) => { - impl<$l, $E: Output + $l, T: ViewContext<$l, $E>> TryFromDsl<$l, T> for $Struct { - fn try_from_expr ($state: &$l T, $iter: TokenIter<'a>) -> Option { + (< + $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: 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 b9ececc..456d797 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -68,8 +68,10 @@ impl ToTokens for CommandDef { } #block /// Generated by [tengri_proc]. - impl<'a> TryFromDsl<'a, #target> for #enumeration { - fn try_from_expr (state: &#target, iter: TokenIter) -> Option { + impl<'state> TryFromDsl<'state, #target> for #enumeration { + fn try_from_expr <'source: 'state> ( + state: &'state #target, iter: TokenIter<'source> + ) -> Option { let mut iter = iter.clone(); match iter.next() { #(#matchers)* @@ -148,11 +150,10 @@ impl CommandArm { 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 = LitStr::new(&format!("{}: missing value \"{}\" ({})", + let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { - #arg : Context::get(state, &iter.next().expect(#take_err).value) - .expect(#give_err) , + #arg: Context::get(state, &mut iter).expect(#give_err), }); } out diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index affb192..15e08e8 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -66,8 +66,8 @@ impl ToTokens for ExposeImpl { let formatted_type = format!("{}", quote! { #t }); let predefined = match formatted_type.as_str() { "bool" => quote! { - ::tengri::dsl::Value::Sym(":true") => true, - ::tengri::dsl::Value::Sym(":false") => false, + Some(::tengri::dsl::Value::Sym(":true")) => true, + Some(::tengri::dsl::Value::Sym(":false")) => false, }, "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize" => { @@ -76,7 +76,7 @@ impl ToTokens for ExposeImpl { Span::call_site() ); quote! { - ::tengri::dsl::Value::Num(n) => TryInto::<#t>::try_into(*n) + Some(::tengri::dsl::Value::Num(n)) => TryInto::<#t>::try_into(n) .unwrap_or_else(|_|panic!(#num_err)), } }, @@ -85,9 +85,11 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc]. - impl ::tengri::dsl::Context<#t> for #target { - fn get (&self, dsl: &::tengri::dsl::Value) -> Option<#t> { - Some(match dsl { + 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 None @@ -113,7 +115,7 @@ impl ToTokens for ExposeArm { let Self(key, value) = self; let key = LitStr::new(&key, Span::call_site()); write_quote_to(out, quote! { - ::tengri::dsl::Value::Sym(#key) => self.#value() + Some(::tengri::dsl::Value::Sym(#key)) => self.#value() }) } } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index c70cb31..b62d976 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -55,8 +55,10 @@ impl ToTokens for ViewDef { } } /// Generated by [tengri_proc]. - impl<'a> ::tengri::output::ViewContext<'a, #output> for #ident { - fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + 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!("expected Sym(content), got: {value:?}") From ab07fd2b4303e1c91325aa3f5bc0014ec5a82961 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 19:45:25 +0300 Subject: [PATCH 047/178] dsl: compact --- dsl/src/dsl.rs | 324 ++++++++++++++++++++++++++++++++++++ dsl/src/dsl_context.rs | 40 ----- dsl/src/dsl_error.rs | 15 -- dsl/src/dsl_iter.rs | 157 ----------------- dsl/src/dsl_macros.rs | 46 ----- dsl/src/dsl_token.rs | 120 ------------- dsl/src/lib.rs | 39 ++++- output/src/ops/transform.rs | 6 +- 8 files changed, 363 insertions(+), 384 deletions(-) create mode 100644 dsl/src/dsl.rs delete mode 100644 dsl/src/dsl_context.rs delete mode 100644 dsl/src/dsl_error.rs delete mode 100644 dsl/src/dsl_iter.rs delete mode 100644 dsl/src/dsl_macros.rs delete mode 100644 dsl/src/dsl_token.rs diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs new file mode 100644 index 0000000..b3d9448 --- /dev/null +++ b/dsl/src/dsl.rs @@ -0,0 +1,324 @@ +use crate::*; +use thiserror::Error; + +pub type ParseResult = Result; + +#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError { + #[error("parse failed: not implemented")] + Unimplemented, + #[error("parse failed: empty")] + Empty, + #[error("parse failed: incomplete")] + Incomplete, + #[error("parse failed: unexpected character '{0}'")] + Unexpected(char), + #[error("parse failed: error #{0}")] + Code(u8), +} + +pub trait TryFromDsl<'state, T>: Sized { + fn try_from_expr <'source: 'state> ( + _state: &'state T, _iter: TokenIter<'source> + ) -> Option { + None + } + fn try_from_atom <'source: 'state> ( + state: &'state T, value: Value<'source> + ) -> Option { + if let Exp(0, iter) = value { + return Self::try_from_expr(state, iter.clone()) + } + None + } +} + +pub trait TryIntoDsl: Sized { + fn try_into_atom (&self) -> Option; +} + +/// 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 + } +} + +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() + } +} + +/// Implement the const iterator pattern. +#[macro_export] macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } + } +} + +/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] +/// [TokenIter::next] returns just the [Token] and mutates `self`, +/// instead of returning an updated version of the struct as [SourceIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct TokenIter<'a>( + pub SourceIter<'a> +); + +impl<'a> TokenIter<'a> { + pub const fn new (source: &'a str) -> Self { + Self(SourceIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} + +impl<'a> Iterator for TokenIter<'a> { + type Item = Token<'a>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{self.0 = rest; item}) + } +} + +impl<'a> From<&'a str> for TokenIter<'a> { + fn from (source: &'a str) -> Self{ + Self(SourceIter(source)) + } +} + +impl<'a> From> for TokenIter<'a> { + fn from (source: SourceIter<'a>) -> Self{ + Self(source) + } +} + +/// Owns a reference to the source text. +/// [SourceIter::next] emits subsequent pairs of: +/// * a [Token] and +/// * the source text remaining +/// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceIter<'a>(pub &'a str); + +const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); + +impl<'a> From<&'a str> for SourceIter<'a> { + fn from (source: &'a str) -> Self{ + Self::new(source) + } +} + +impl<'a> SourceIter<'a> { + pub const fn new (source: &'a str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(Token<'a>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} +/// Static iteration helper. +#[macro_export] macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +pub const fn peek_src <'a> (source: &'a str) -> Option> { + let mut token: Token<'a> = Token::new(source, 0, 0, Nil); + iterate!(char_indices(source) => (start, c) => token = match token.value() { + Err(_) => return Some(token), + Nil => match c { + ' '|'\n'|'\r'|'\t' => + token.grow(), + '(' => + Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))), + '"' => + Token::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + Token::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + Token::new(source, start, 1, Key(str_range(source, start, start + 1))), + '0'..='9' => + Token::new(source, start, 1, match to_digit(c) { + Ok(c) => Value::Num(c), + Result::Err(e) => Value::Err(e) + }), + _ => token.error(Unexpected(c)) + }, + Str(_) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, + Num(n) => match c { + '0'..='9' => token.grow_num(n, c), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Sym(_) => match c { + 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Key(_) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Exp(depth, _) => match depth { + 0 => return Some(token.grow_exp()), + _ => match c { + ')' => token.grow_out(), + '(' => token.grow_in(), + _ => token.grow_exp(), + } + }, + }); + match token.value() { + Nil => None, + _ => Some(token), + } +} + +pub const fn to_number (digits: &str) -> Result { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) +} + +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { + pub source: &'source str, + pub start: usize, + pub length: usize, + pub value: Value<'source>, +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { + #[default] Nil, + Err(ParseError), + Num(usize), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, TokenIter<'source>), +} + +impl<'source> Token<'source> { + pub const fn new (source: &'source str, start: usize, length: usize, value: Value<'source>) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.source) + //str_range(self.source, self.start, self.end()) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn value (&self) -> Value { + self.value + } + pub const fn error (self, error: ParseError) -> Self { + Self { value: Value::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) { + Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, + Result::Err(e) => Self { value: Err(e), ..self.grow() }, + } + } + pub const fn grow_key (self) -> Self { + let mut token = self.grow(); + token.value = Key(token.slice_source(self.source)); + token + } + pub const fn grow_sym (self) -> Self { + let mut token = self.grow(); + token.value = Sym(token.slice_source(self.source)); + token + } + pub const fn grow_str (self) -> Self { + let mut token = self.grow(); + token.value = Str(token.slice_source(self.source)); + token + } + pub const fn grow_exp (self) -> Self { + let mut token = self.grow(); + if let Exp(depth, _) = token.value { + token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source))); + } else { + unreachable!() + } + token + } + pub const fn grow_in (self) -> Self { + let mut token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + token.value = Value::Exp(depth.saturating_add(1), source) + } else { + unreachable!() + } + token + } + pub const fn grow_out (self) -> Self { + let mut token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + if depth > 0 { + token.value = Value::Exp(depth - 1, source) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + token + } +} diff --git a/dsl/src/dsl_context.rs b/dsl/src/dsl_context.rs deleted file mode 100644 index 4acbf63..0000000 --- a/dsl/src/dsl_context.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; - -pub trait TryFromDsl<'state, T>: Sized { - fn try_from_expr <'source: 'state> ( - _state: &'state T, _iter: TokenIter<'source> - ) -> Option { - None - } - fn try_from_atom <'source: 'state> ( - state: &'state T, value: Value<'source> - ) -> Option { - if let Exp(0, iter) = value { - return Self::try_from_expr(state, iter.clone()) - } - None - } -} - -pub trait TryIntoDsl: Sized { - fn try_into_atom (&self) -> Option; -} - -/// 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 - } -} - -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() - } -} diff --git a/dsl/src/dsl_error.rs b/dsl/src/dsl_error.rs deleted file mode 100644 index 40b687d..0000000 --- a/dsl/src/dsl_error.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::*; -use thiserror::Error; -pub type ParseResult = Result; -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError { - #[error("parse failed: not implemented")] - Unimplemented, - #[error("parse failed: empty")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), -} diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs deleted file mode 100644 index acdbe4b..0000000 --- a/dsl/src/dsl_iter.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! The token iterator [TokenIter] allows you to get the -//! general-purpose syntactic [Token]s represented by the source text. -//! -//! Both iterators are `peek`able: -//! -//! ``` -//! let src = include_str!("../test.edn"); -//! let mut view = tengri_dsl::TokenIter::new(src); -//! assert_eq!(view.0.0, src); -//! assert_eq!(view.peek(), view.0.peek()) -//! ``` -use crate::*; - -/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] -/// [TokenIter::next] returns just the [Token] and mutates `self`, -/// instead of returning an updated version of the struct as [SourceIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct TokenIter<'a>( - pub SourceIter<'a> -); - -impl<'a> TokenIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(SourceIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'a> Iterator for TokenIter<'a> { - type Item = Token<'a>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{self.0 = rest; item}) - } -} - -impl<'a> From<&'a str> for TokenIter<'a> { - fn from (source: &'a str) -> Self{ - Self(SourceIter(source)) - } -} - -impl<'a> From> for TokenIter<'a> { - fn from (source: SourceIter<'a>) -> Self{ - Self(source) - } -} - -/// Owns a reference to the source text. -/// [SourceIter::next] emits subsequent pairs of: -/// * a [Token] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'a>(pub &'a str); - -const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); - -impl<'a> From<&'a str> for SourceIter<'a> { - fn from (source: &'a str) -> Self{ - Self::new(source) - } -} - -impl<'a> SourceIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(Token<'a>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -pub const fn peek_src <'a> (source: &'a str) -> Option> { - let mut token: Token<'a> = Token::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))), - '"' => - Token::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - Token::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - Token::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - Token::new(source, start, 1, match to_digit(c) { - Ok(c) => Value::Num(c), - Result::Err(e) => Value::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -pub const fn to_number (digits: &str) -> Result { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> Result { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs deleted file mode 100644 index 93b2cea..0000000 --- a/dsl/src/dsl_macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -#[macro_export] macro_rules! get_value { - ($state:expr => $token:expr) => { - if let Some(value) = $state.get(&$token.value) { - value - } else { - panic!("no value corresponding to {:?}", &$token.value); - } - } -} - -#[macro_export] macro_rules! get_content { - ($state:expr => $token:expr) => { - if let Some(content) = $state.get_content(&$token.value) { - content - } else { - panic!("no content corresponding to {:?}", &$token.value); - } - } -} - diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs deleted file mode 100644 index e26ee93..0000000 --- a/dsl/src/dsl_token.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! [Token]s are parsed substrings with an associated [Value]. -//! -//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -//! * [Value::Exp] wraps an expression depth and a [SourceIter] -//! with the remaining part of the expression. -//! * expression depth other that 0 mean unclosed parenthesis. -//! * closing and unopened parenthesis panics during reading. -//! * [ ] TODO: signed depth might be interesting -//! * [Value::Sym] and [Value::Key] are stringish literals -//! with slightly different parsing rules. -//! * [Value::Num] is an unsigned integer literal. -//!``` -//! use tengri_dsl::{*, Value::*}; -//! let source = include_str!("../test.edn"); -//! let mut view = TokenIter::new(source); -//! assert_eq!(view.peek(), Some(Token { -//! source, -//! start: 0, -//! length: source.len(), -//! value: Exp(0, TokenIter::new(&source[1..])) -//! })); -//!``` -use crate::*; - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { - pub source: &'source str, - pub start: usize, - pub length: usize, - pub value: Value<'source>, -} - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { - #[default] Nil, - Err(ParseError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, TokenIter<'source>), -} - -impl<'source> Token<'source> { - pub const fn new (source: &'source str, start: usize, length: usize, value: Value<'source>) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.source) - //str_range(self.source, self.start, self.end()) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn value (&self) -> Value { - self.value - } - pub const fn error (self, error: ParseError) -> Self { - Self { value: Value::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) { - Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let mut token = self.grow(); - token.value = Key(token.slice_source(self.source)); - token - } - pub const fn grow_sym (self) -> Self { - let mut token = self.grow(); - token.value = Sym(token.slice_source(self.source)); - token - } - pub const fn grow_str (self) -> Self { - let mut token = self.grow(); - token.value = Str(token.slice_source(self.source)); - token - } - pub const fn grow_exp (self) -> Self { - let mut token = self.grow(); - if let Exp(depth, _) = token.value { - token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source))); - } else { - unreachable!() - } - token - } - pub const fn grow_in (self) -> Self { - let mut token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - token.value = Value::Exp(depth.saturating_add(1), source) - } else { - unreachable!() - } - token - } - pub const fn grow_out (self) -> Self { - let mut token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - if depth > 0 { - token.value = Value::Exp(depth - 1, source) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - token - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 411974a..52f8281 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,3 +1,36 @@ +//! [Token]s are parsed substrings with an associated [Value]. +//! +//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] +//! * [Value::Exp] wraps an expression depth and a [SourceIter] +//! with the remaining part of the expression. +//! * expression depth other that 0 mean unclosed parenthesis. +//! * closing and unopened parenthesis panics during reading. +//! * [ ] TODO: signed depth might be interesting +//! * [Value::Sym] and [Value::Key] are stringish literals +//! with slightly different parsing rules. +//! * [Value::Num] is an unsigned integer literal. +//!``` +//! use tengri_dsl::{*, Value::*}; +//! let source = include_str!("../test.edn"); +//! let mut view = TokenIter::new(source); +//! assert_eq!(view.peek(), Some(Token { +//! source, +//! start: 0, +//! length: source.len(), +//! value: Exp(0, TokenIter::new(&source[1..])) +//! })); +//!``` +//! The token iterator [TokenIter] allows you to get the +//! general-purpose syntactic [Token]s represented by the source text. +//! +//! Both iterators are `peek`able: +//! +//! ``` +//! let src = include_str!("../test.edn"); +//! let mut view = tengri_dsl::TokenIter::new(src); +//! assert_eq!(view.0.0, src); +//! assert_eq!(view.peek(), view.0.peek()) +//! ``` #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] @@ -8,11 +41,7 @@ pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use std::fmt::Debug; -mod dsl_error; pub use self::dsl_error::*; -mod dsl_token; pub use self::dsl_token::*; -mod dsl_iter; pub use self::dsl_iter::*; -mod dsl_context; pub use self::dsl_context::*; -mod dsl_macros; +mod dsl; pub use self::dsl::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 0dd2264..611d22b 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -43,7 +43,11 @@ macro_rules! transform_xy { if k == $x || k == $y || k == $xy { let _ = iter.next().unwrap(); let token = iter.next().expect("no content specified"); - let content = get_content!(state => token); + 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), From 60c077102421ed8a7d6cb8674e4380ec6396ca84 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 20:02:24 +0300 Subject: [PATCH 048/178] proc, input, output: cleanup warnings --- input/src/input_dsl.rs | 3 +-- output/src/ops/cond.rs | 48 ++++++++++++++------------------------- proc/src/lib.rs | 14 ++++-------- proc/src/proc_command.rs | 49 ++++++++-------------------------------- proc/src/proc_expose.rs | 2 +- proc/src/proc_view.rs | 9 -------- 6 files changed, 32 insertions(+), 93 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index bb5346d..0095579 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -132,8 +132,7 @@ where 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()); - Ok(()) + write!(f, "[InputMap: {} layer(s)]", self.layers.len()) } } diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 521fdf8..5e71bef 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -22,18 +22,13 @@ impl Either { try_from_expr!(<'source, 'state, E>: When>: |state, iter| { if let Some(Token { value: Value::Key("when"), .. }) = iter.peek() { let _ = iter.next().unwrap(); - - let condition = iter.next().expect("no condition specified"); - let condition = state.get(&mut iter).expect("no condition provided"); - - 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 for {:?}", &content); - }; - - return Some(Self(condition, content)) + let content = iter.next().expect("no content specified").value; + return Some(Self( + state.get(&mut iter) + .expect("no condition provided"), + state.get_content(&content) + .unwrap_or_else(||panic!("no content corresponding to for {:?}", &content)) + )) } }); @@ -41,25 +36,16 @@ try_from_expr!(<'source, 'state, E>: When>: |state, iter| { try_from_expr!(<'source, 'state, E>: Either, RenderBox<'state, E>>: |state, iter| { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { let _ = iter.next().unwrap(); - - let condition = iter.next().expect("no condition specified"); - let condition = state.get(&mut iter).expect("no condition provided"); - - let content = iter.next().expect("no content specified"); - let content = if let Some(content) = state.get_content(&content.value) { - content - } else { - panic!("no content 1 corresponding to {:?}", &content); - }; - - let alternate = iter.next().expect("no alternate specified"); - let alternate = if let Some(alternate) = state.get_content(&alternate.value) { - alternate - } else { - panic!("no content 2 corresponding to {:?}", &alternate); - }; - - return Some(Self(condition, content, alternate)) + let content = iter.next().expect("no content specified").value; + let alternate = iter.next().expect("no alternate specified").value; + return Some(Self( + state.get(&mut iter) + .expect("no condition provided"), + state.get_content(&content) + .unwrap_or_else(||panic!("no content 1 corresponding to {:?}", &content)), + state.get_content(&alternate) + .unwrap_or_else(||panic!("no content 2 corresponding to {:?}", &alternate)), + )) } }); diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 66dc0c7..f566fa0 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -2,23 +2,17 @@ #![feature(box_patterns)] extern crate proc_macro; -pub(crate) use std::collections::{BTreeMap, BTreeSet}; +pub(crate) use std::collections::BTreeMap; pub(crate) use std::cmp::Ordering; pub(crate) use std::sync::Arc; pub(crate) use proc_macro::TokenStream; pub(crate) use proc_macro2::{ - TokenStream as TokenStream2, TokenTree, - Ident, Span, Punct, Spacing::*, Group, Delimiter, Literal + TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::* }; pub(crate) use syn::{ - parse, parse_macro_input, parse_quote as pq, - braced, bracketed, parenthesized, Token, - Arm, Expr, Attribute, Meta, MetaList, Path, PathSegment, PathArguments, - ImplItem, ImplItemFn, LitStr, Type, ItemImpl, ReturnType, Signature, FnArg, - Pat, PatType, PatIdent, + parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, + ItemImpl, ReturnType, Signature, FnArg, Pat, PatType, PatIdent, parse::{Parse, ParseStream, Result}, - token::{PathSep, Brace}, - punctuated::Punctuated, }; pub(crate) use quote::{quote, TokenStreamExt, ToTokens}; pub(crate) use heck::{AsKebabCase, AsUpperCamelCase}; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 456d797..8d9d345 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -121,13 +121,12 @@ impl CommandArm { Some((arg, ty)) } else { unreachable!("only typed args should be present at this position"); - None }) } fn to_enum_variant_def (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); - let ident = &self.0; + //let ident = &self.0; if self.has_args() { out.append(Group::new(Delimiter::Brace, { let mut out = TokenStream2::new(); @@ -148,8 +147,8 @@ impl CommandArm { 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 take_err = LitStr::new(&format!("{}: missing argument \"{}\" ({})", + //quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { @@ -164,11 +163,11 @@ impl CommandArm { fn to_enum_variant_unbind (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); - let ident = &self.0; + //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() { + for (arg, _ty) in self.args() { write_quote_to(&mut out, quote! { #arg , }); } out @@ -189,8 +188,10 @@ impl CommandArm { fn to_implementation (&self) -> TokenStream2 { let ident = &self.0; let variant = self.to_enum_variant_unbind(); - let mut give_rest = write_quote(quote! { /*TODO*/ }); - let give_args = self.args().map(|(arg, ty)|write_quote(quote! { #arg, })).collect::>(); + 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), }) } } @@ -216,35 +217,3 @@ impl ToTokens for CommandVariant { out.append(Punct::new(',', Alone)); } } - -impl ToTokens for CommandArm { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self(ident, args, returnType) = self; - for ident in ["tengri", "dsl", "Value", "Sym"].iter() { - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(Ident::new(ident, Span::call_site())); - } - out.append(Group::new(Delimiter::Parenthesis, { - let mut out = TokenStream2::new(); - out.append(self.to_enum_variant_ident()); - out - })); - out.append(Punct::new('=', Joint)); - out.append(Punct::new('>', Alone)); - out.append(Ident::new("Self", Span::call_site())); - out.append(Punct::new(':', Joint)); - out.append(Punct::new(':', Alone)); - out.append(ident.clone()); - out.append(Group::new(Delimiter::Parenthesis, { - let mut out = TokenStream2::new(); - for arg in args.iter() { - // TODO - out.append(LitStr::new(&self.to_key(), Span::call_site()).token()); - out.append(Punct::new(',', Alone)); - } - out - })); - out.append(Punct::new(',', Alone)); - } -} diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 15e08e8..36fc418 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -19,7 +19,7 @@ struct ExposeSym(LitStr); struct ExposeType(Box); impl Parse for ExposeMeta { - fn parse (input: ParseStream) -> Result { + fn parse (_input: ParseStream) -> Result { Ok(Self) } } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index b62d976..a1166ae 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -146,15 +146,6 @@ impl ToTokens for ViewArm { //} //} -fn nth_segment_is (segments: &Punctuated, n: usize, x: &str) -> bool { - if let Some(PathSegment { arguments: PathArguments::None, ident, .. }) = segments.get(n) { - if format!("{ident}") == x { - return true - } - } - return false -} - //impl std::cmp::PartialEq for ViewItem { //fn eq (&self, other: &Self) -> bool { //self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) From 3bb38f2d27afc294349f72b1a2f7936bc9338d4f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 20:21:31 +0300 Subject: [PATCH 049/178] proc: view: list available on error --- proc/src/proc_view.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index a1166ae..25ec680 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -45,7 +45,16 @@ impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let ident = &block.self_ty; - let exposed: Vec<_> = exposed.iter().map(|(k,v)|ViewArm(k.clone(), v.clone())).collect(); + let mut available = vec![]; + let exposed: Vec<_> = exposed.iter().map(|(k,v)|{ + available.push(k.clone()); + ViewArm(k.clone(), v.clone()) + }).collect(); + let available: String = available.join(", "); + let error_msg = LitStr::new( + &format!("expected Sym(content), got: {{value:?}}, available: {available}"), + Span::call_site() + ); for token in quote! { #block /// Generated by [tengri_proc]. @@ -59,10 +68,7 @@ impl ToTokens for ViewDef { fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) -> Option> { - match value { - #(#exposed)* - _ => panic!("expected Sym(content), got: {value:?}") - } + match value { #(#exposed)* _ => panic!(#error_msg) } } } } { From 20ccff13de1957c1268c6fec64048d8ae1767fd5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 21:13:46 +0300 Subject: [PATCH 050/178] proc: auto implement Context on command target Context and TryFromDsl overlap --- dsl/src/dsl.rs | 10 +++------- input/src/input_dsl.rs | 4 ++-- output/src/ops/transform.rs | 4 ++-- output/src/view.rs | 2 +- proc/src/proc_command.rs | 10 +++++++++- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index b3d9448..f4eedaf 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -18,24 +18,20 @@ pub type ParseResult = Result; pub trait TryFromDsl<'state, T>: Sized { fn try_from_expr <'source: 'state> ( - _state: &'state T, _iter: TokenIter<'source> + _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, iter) = value { - return Self::try_from_expr(state, iter.clone()) + if let Exp(0, mut iter) = value { + return Self::try_from_expr(state, &mut iter) } None } } -pub trait TryIntoDsl: Sized { - fn try_into_atom (&self) -> Option; -} - /// 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 { diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 0095579..cde5a15 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -29,7 +29,7 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { + if let Some(command) = C::try_from_expr(state, &mut exp_iter) { return Some(command) } } @@ -55,7 +55,7 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, exp_iter.clone()) { + if let Some(command) = C::try_from_expr(state, &mut exp_iter) { return Some(command) } } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 611d22b..904eba4 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -35,7 +35,7 @@ macro_rules! transform_xy { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> for $Enum> { - fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) + fn try_from_expr <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Option { let mut iter = iter.clone(); @@ -88,7 +88,7 @@ macro_rules! transform_xy_unit { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> for $Enum> { - fn try_from_expr <'source: 'state> (state: &'state T, iter: TokenIter<'source>) -> Option { + 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 { diff --git a/output/src/view.rs b/output/src/view.rs index 4bf3d73..f2e9ef4 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -103,7 +103,7 @@ pub trait ViewContext<'state, E: Output + 'state>: Send + Sync > TryFromDsl<$lt_state, T> for $Struct { fn try_from_expr <$lt_source: $lt_state> ( $state: &$lt_state T, - $iter: TokenIter<$lt_source> + $iter: &mut TokenIter<$lt_source> ) -> Option { let mut $iter = $iter.clone(); $body; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 8d9d345..9504f91 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -70,7 +70,7 @@ impl ToTokens for CommandDef { /// Generated by [tengri_proc]. impl<'state> TryFromDsl<'state, #target> for #enumeration { fn try_from_expr <'source: 'state> ( - state: &'state #target, iter: TokenIter<'source> + state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> ) -> Option { let mut iter = iter.clone(); match iter.next() { @@ -80,6 +80,14 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc]. + impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { + fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) + -> Option<#enumeration> + { + #enumeration::try_from_expr(self, iter) + } + } + /// Generated by [tengri_proc]. impl Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { From fe8ecf8a9822cafdd43e194fc25b681db09df62c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 22:43:12 +0300 Subject: [PATCH 051/178] input, proc: add full paths in macros --- input/src/input_handle.rs | 8 +++++--- proc/src/proc_command.rs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs index 2a8fbcb..99bce29 100644 --- a/input/src/input_handle.rs +++ b/input/src/input_handle.rs @@ -18,15 +18,17 @@ pub trait Input: Send + Sync + Sized { /// Implement the [Handle] trait. #[macro_export] macro_rules! handle { (|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl Handle for $Struct { + impl ::tengri::input::Handle for $Struct { fn handle (&mut $self, $input: &E) -> Perhaps { $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> { + impl ::tengri::input::Handle<$E> for $Struct { + fn handle (&mut $self, $input: &$E) -> + Perhaps<<$E as ::tengri::input::Input>::Handled> + { $handler } } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 9504f91..cc5b134 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -68,7 +68,7 @@ impl ToTokens for CommandDef { } #block /// Generated by [tengri_proc]. - impl<'state> TryFromDsl<'state, #target> for #enumeration { + 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 { @@ -88,7 +88,7 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc]. - impl Command<#target> for #enumeration { + impl ::tengri::input::Command<#target> for #enumeration { fn execute (self, state: &mut #target) -> Perhaps { match self { #(#implementations)* @@ -160,7 +160,7 @@ impl CommandArm { let give_err = LitStr::new(&format!("{}: missing value for \"{}\" ({})", quote!{#ident}, quote!{#arg}, quote!{#ty}), Span::call_site()); write_quote_to(&mut out, quote! { - #arg: Context::get(state, &mut iter).expect(#give_err), + #arg: ::tengri::dsl::Context::get(state, &mut iter).expect(#give_err), }); } out From 4a385b40ff778b4f23b4a989da94d45a88137ba0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 22:43:21 +0300 Subject: [PATCH 052/178] test subcommand handling --- Cargo.lock | 3 +++ tengri/Cargo.toml | 5 +++++ tengri/src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d9ccc29..0ba0ef4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -936,9 +936,12 @@ dependencies = [ name = "tengri" version = "0.13.0" dependencies = [ + "crossterm", + "tengri", "tengri_dsl", "tengri_input", "tengri_output", + "tengri_proc", "tengri_tui", ] diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index 1c7d4eb..23e87f8 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -10,6 +10,11 @@ tengri_input = { optional = true, path = "../input" } tengri_output = { optional = true, path = "../output" } tengri_tui = { optional = true, path = "../tui" } +[dev-dependencies] +tengri_proc = { path = "../proc" } +tengri = { path = ".", features = [ "dsl" ] } +crossterm = "0.28.1" + [features] default = [ "input", "output", "tui" ] input = [ "tengri_input" ] diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index c1c72a0..7703a14 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -2,3 +2,59 @@ #[cfg(feature="input")] pub use ::tengri_input as input; #[cfg(feature="dsl")] pub use ::tengri_dsl as dsl; #[cfg(feature="tui")] pub use ::tengri_tui as tui; + +#[cfg(test)] extern crate tengri_proc; +#[cfg(test)] #[test] fn test_subcommand () -> crate::output::Usually<()> { + use crate::output::Perhaps; + use crate::input::{Command, InputMap, KeyMap, Handle, handle}; + use crate::dsl::{TryFromDsl, TokenIter}; + use crate::tui::TuiIn; + use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; + //use crate::input::*; + //use crate::dsl::*; + struct Test { + keys: InputMap<'static, Test, TestCommand, TuiIn, TokenIter<'static>> + } + 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 { + Ok(None) + } + fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { + Ok(command.execute(state)?.map(|command|Self::DoSub { command })) + } + } + #[tengri_proc::command(Test)] impl TestSubcommand { + fn do_other_thing (state: &mut Test) -> Perhaps { + Ok(None) + } + } + let mut test = Test { + keys: InputMap::new( + "(@a do-thing) (@b do-sub do-other-thing)".into() + ) + }; + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('a'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('b'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('c'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + Ok(()) +} From cb8fd26922fd1cfad4ceadeb89e48544531a178e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 9 May 2025 23:00:36 +0300 Subject: [PATCH 053/178] collect tests --- dsl/src/lib.rs | 16 ---- output/src/lib.rs | 1 - output/src/ops/bsp.rs | 6 +- proc/src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++-- proc/src/proc_expose.rs | 106 ---------------------- proc/src/proc_view.rs | 122 +------------------------- tengri/src/lib.rs | 47 +++++++++- tui/examples/tui.rs | 3 + 8 files changed, 234 insertions(+), 257 deletions(-) diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 52f8281..c685572 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -143,22 +143,6 @@ mod dsl; pub use self::dsl::*; Ok(()) } -#[cfg(test)] #[test] fn test_dsl_context () { - struct Test; - #[tengri_proc::expose] - impl Test { - fn some_bool (&self) -> bool { - true - } - } - assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); - assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); - assert_eq!(Test.get(&Value::Num(0)), Some(false)); - assert_eq!(Test.get(&Value::Num(1)), Some(true)); -} - //#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { //// Let's pretend to render some view. //let source = include_str!("../../tek/src/view_arranger.edn"); diff --git a/output/src/lib.rs b/output/src/lib.rs index 064d8da..45bf90a 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -64,7 +64,6 @@ pub type Perhaps = Result, Box>; #[cfg(test)] mod test { use crate::*; use proptest::{prelude::*, option::of}; - use proptest::option::of; proptest! { #[test] fn proptest_direction ( diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 0ab80c3..fe48efc 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -1,7 +1,11 @@ use crate::*; pub use Direction::*; /// A split or layer. -pub struct Bsp(Direction, X, Y); +pub struct Bsp( + pub(crate) Direction, + pub(crate) A, + pub(crate) B, +); impl, B: Content> Content for Bsp { fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); diff --git a/proc/src/lib.rs b/proc/src/lib.rs index f566fa0..132d416 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -21,14 +21,7 @@ mod proc_view; mod proc_expose; mod proc_command; -#[proc_macro_attribute] -pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { - use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; - write(ViewDef( - parse_macro_input!(meta as ViewMeta), - parse_macro_input!(item as ViewImpl), - )) -} +#[cfg(test)] use syn::parse_quote as pq; #[proc_macro_attribute] pub fn expose (meta: TokenStream, item: TokenStream) -> TokenStream { @@ -48,6 +41,15 @@ pub fn command (meta: TokenStream, item: TokenStream) -> TokenStream { )) } +#[proc_macro_attribute] +pub fn view (meta: TokenStream, item: TokenStream) -> TokenStream { + use self::proc_view::{ViewDef, ViewMeta, ViewImpl}; + write(ViewDef( + parse_macro_input!(meta as ViewMeta), + parse_macro_input!(item as ViewImpl), + )) +} + pub(crate) fn write (t: T) -> TokenStream { let mut out = TokenStream2::new(); t.to_tokens(&mut out); @@ -67,3 +69,175 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { out.append(token); } } + +#[cfg(test)] #[test] fn test_proc_view () { + let x: crate::proc_view::ViewMeta = pq! { SomeOutput }; + let output: Ident = pq! { SomeOutput }; + assert_eq!(x.output, output); + + // TODO + let x: crate::proc_view::ViewImpl = pq! { + impl Foo { + /// docstring1 + #[tengri::view(":view1")] #[bar] fn a_view () {} + + #[baz] + /// docstring2 + #[baz] fn is_not_view () {} + } + }; + let expected_target: Ident = pq! { Foo }; + //assert_eq!(x.target, expected_target); + //assert_eq!(x.items.len(), 2); + //assert_eq!(x.items[0].item, pq! { + ///// docstring1 + //#[bar] fn a_view () {} + //}); + //assert_eq!(x.items[1].item, pq! { + //#[baz] + ///// docstring2 + //#[baz] fn is_not_view () {} + //}); + //assert_eq!(x.syms, vec![ + //ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, }, + //]); + // FIXME + //let parsed: ViewDefinition = pq! { + //#[tengri_proc::view(SomeOutput)] + //impl SomeView { + //#[tengri::view(":view-1")] + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + //}; + //let written = quote! { #parsed }; + //assert_eq!(format!("{written}"), format!("{}", quote! { + //impl SomeView { + //fn view_1 (&self) -> impl Content + use<'_> { + //"view-1" + //} + //} + ///// Generated by [tengri_proc]. + //impl ::tengri::output::Content for SomeView { + //fn content (&self) -> impl Render { + //self.size.of(::tengri::output::View(self, self.config.view)) + //} + //} + ///// Generated by [tengri_proc]. + //impl<'a> ::tengri::dsl::ViewContext<'a, SomeOutput> for SomeView { + //fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { + //match value { + //::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(), + //_ => panic!("expected Sym(content), got: {value:?}") + //} + //} + //} + //})); +} + +//#[cfg(test)] #[test] fn test_expose_definition () { + // TODO + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //fn something () -> bool {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////); + + //let parsed: ExposeImpl = pq! { + ////#[tengri_proc::expose] + //impl Something { + //#[tengri::expose(bool)] { + //":bool1" => true || false, + //} + //#[tengri::expose(u16)] { + //":u161" => 0 + 1, + //} + //#[tengri::expose(usize)] { + //":usize1" => 1 + 2, + //} + //#[tengri::expose(Arc)] { + //":arcstr1" => "foo".into(), + //} + //#[tengri::expose(Option>)] { + //":optarcstr1" => Some("bar".into()), + //":optarcstr2" => Some("baz".into()), + //} + //fn something () {} + //} + //}; + //// FIXME: + ////assert_eq!( + ////format!("{}", quote! { #parsed }), + ////format!("{}", quote! { + ////impl Something { + ////fn something () {} + ////} + ////impl ::tengri::Context> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option> { + ////Some(match dsl { + ////::tengri::Value::Sym(":arcstr1") => "foo".into(), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context>> for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option>> { + ////Some(match dsl { + ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), + ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Sym(":true") => true, + ////::tengri::Value::Sym(":false") => false, + ////::tengri::Value::Sym(":bool1") => true || false, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as u16, + ////::tengri::Value::Sym(":u161") => 0 + 1, + ////_ => return None + ////}) + ////} + ////} + ////impl ::tengri::Context for Something { + ////fn get (&self, dsl: &::tengri::Value) -> Option { + ////Some(match dsl { + ////::tengri::Value::Num(n) => *n as usize, + ////::tengri::Value::Sym(":usize1") => 1 + 2, + ////_ => return None + ////}) + ////} + ////} + ////}) + ////) +//} diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 36fc418..4a11347 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -189,109 +189,3 @@ impl ToTokens for ExposeType { self.0.to_tokens(out) } } - -#[cfg(test)] #[test] fn test_expose_definition () { - // TODO - //let parsed: ExposeImpl = pq! { - ////#[tengri_proc::expose] - //impl Something { - //fn something () -> bool {} - //} - //}; - //// FIXME: - ////assert_eq!( - ////format!("{}", quote! { #parsed }), - ////format!("{}", quote! { - ////impl Something { - ////fn something () {} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Sym(":true") => true, - ////::tengri::Value::Sym(":false") => false, - ////::tengri::Value::Sym(":bool1") => true || false, - ////_ => return None - ////}) - ////} - ////} - ////}) - ////); - - //let parsed: ExposeImpl = pq! { - ////#[tengri_proc::expose] - //impl Something { - //#[tengri::expose(bool)] { - //":bool1" => true || false, - //} - //#[tengri::expose(u16)] { - //":u161" => 0 + 1, - //} - //#[tengri::expose(usize)] { - //":usize1" => 1 + 2, - //} - //#[tengri::expose(Arc)] { - //":arcstr1" => "foo".into(), - //} - //#[tengri::expose(Option>)] { - //":optarcstr1" => Some("bar".into()), - //":optarcstr2" => Some("baz".into()), - //} - //fn something () {} - //} - //}; - //// FIXME: - ////assert_eq!( - ////format!("{}", quote! { #parsed }), - ////format!("{}", quote! { - ////impl Something { - ////fn something () {} - ////} - ////impl ::tengri::Context> for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option> { - ////Some(match dsl { - ////::tengri::Value::Sym(":arcstr1") => "foo".into(), - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context>> for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option>> { - ////Some(match dsl { - ////::tengri::Value::Sym(":optarcstr1") => Some("bar".into()), - ////::tengri::Value::Sym(":optarcstr2") => Some("baz".into()), - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Sym(":true") => true, - ////::tengri::Value::Sym(":false") => false, - ////::tengri::Value::Sym(":bool1") => true || false, - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Num(n) => *n as u16, - ////::tengri::Value::Sym(":u161") => 0 + 1, - ////_ => return None - ////}) - ////} - ////} - ////impl ::tengri::Context for Something { - ////fn get (&self, dsl: &::tengri::Value) -> Option { - ////Some(match dsl { - ////::tengri::Value::Num(n) => *n as usize, - ////::tengri::Value::Sym(":usize1") => 1 + 2, - ////_ => return None - ////}) - ////} - ////} - ////}) - ////) -} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 25ec680..7fdd1fd 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -5,7 +5,7 @@ pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); #[derive(Debug, Clone)] pub(crate) struct ViewMeta { - output: Ident, + pub(crate) output: Ident, } #[derive(Debug, Clone)] @@ -114,123 +114,3 @@ impl ToTokens for ViewArm { out.append(Punct::new(',', Alone)); } } - -//impl ToTokens for ViewSym { - //fn to_tokens (&self, out: &mut TokenStream2) { - //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(self.symbol.clone()); - //out - //})); - //out.append(Punct::new('=', Joint)); - //out.append(Punct::new('>', Alone)); - //out.append(Ident::new("Some", Span::call_site())); - //out.append(Group::new(Delimiter::Parenthesis, { - //let mut out = TokenStream2::new(); - //out.append(Ident::new("self", Span::call_site())); - //out.append(Punct::new('.', Alone)); - //out.append(self.name.clone()); - //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); - //out.append(Punct::new('.', Alone)); - //out.append(Ident::new("boxed", Span::call_site())); - //out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); - //out - //})); - //out.append(Punct::new(',', Alone)); - //} -//} - -//impl std::cmp::PartialEq for ViewItem { - //fn eq (&self, other: &Self) -> bool { - //self.item == other.item && (format!("{:?}", self.expose) == format!("{:?}", other.expose)) - //} -//} - -//impl std::cmp::PartialEq for ViewSym { - //fn eq (&self, other: &Self) -> bool { - //self.name == other.name && (format!("{}", self.symbol) == format!("{}", other.symbol)) - //} -//} - -#[cfg(test)] #[test] fn test_view_meta () { - let x: ViewMeta = pq! { SomeOutput }; - let output: Ident = pq! { SomeOutput }; - assert_eq!(x.output, output); -} - -#[cfg(test)] #[test] fn test_view_impl () { - // TODO - let x: ViewImpl = pq! { - impl Foo { - /// docstring1 - #[tengri::view(":view1")] #[bar] fn a_view () {} - - #[baz] - /// docstring2 - #[baz] fn is_not_view () {} - } - }; - let expected_target: Ident = pq! { Foo }; - //assert_eq!(x.target, expected_target); - //assert_eq!(x.items.len(), 2); - //assert_eq!(x.items[0].item, pq! { - ///// docstring1 - //#[bar] fn a_view () {} - //}); - //assert_eq!(x.items[1].item, pq! { - //#[baz] - ///// docstring2 - //#[baz] fn is_not_view () {} - //}); - //assert_eq!(x.syms, vec![ - //ViewArm( { symbol: pq! { ":view1" }, name: pq! { a_view }, }, - //]); -} - -#[cfg(test)] #[test] fn test_view_definition () { - // FIXME - //let parsed: ViewDefinition = pq! { - //#[tengri_proc::view(SomeOutput)] - //impl SomeView { - //#[tengri::view(":view-1")] - //fn view_1 (&self) -> impl Content + use<'_> { - //"view-1" - //} - //} - //}; - //let written = quote! { #parsed }; - //assert_eq!(format!("{written}"), format!("{}", quote! { - //impl SomeView { - //fn view_1 (&self) -> impl Content + use<'_> { - //"view-1" - //} - //} - ///// Generated by [tengri_proc]. - //impl ::tengri::output::Content for SomeView { - //fn content (&self) -> impl Render { - //self.size.of(::tengri::output::View(self, self.config.view)) - //} - //} - ///// Generated by [tengri_proc]. - //impl<'a> ::tengri::dsl::ViewContext<'a, SomeOutput> for SomeView { - //fn get_content_sym (&'a self, value: &Value<'a>) -> Option> { - //match value { - //::tengri::dsl::Value::Sym(":view-1") => self.view_1().boxed(), - //_ => panic!("expected Sym(content), got: {value:?}") - //} - //} - //} - //})); -} diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 7703a14..53850cd 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -24,6 +24,9 @@ fn do_thing (state: &mut Test) -> Perhaps { Ok(None) } + fn do_thing_arg (state: &mut Test, arg: usize) -> Perhaps { + Ok(None) + } fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { Ok(command.execute(state)?.map(|command|Self::DoSub { command })) } @@ -32,11 +35,17 @@ fn do_other_thing (state: &mut Test) -> Perhaps { Ok(None) } + fn do_other_thing_arg (state: &mut Test, arg: usize) -> Perhaps { + Ok(None) + } } let mut test = Test { - keys: InputMap::new( - "(@a do-thing) (@b do-sub do-other-thing)".into() - ) + keys: InputMap::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, @@ -50,11 +59,41 @@ modifiers: KeyModifiers::NONE, state: KeyEventState::NONE, })))?); - assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { kind: KeyEventKind::Press, code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE, state: KeyEventState::NONE, })))?); + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('d'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('z'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); Ok(()) } + +#[cfg(test)] #[test] fn test_dsl_context () { + use crate::dsl::Value; + + struct Test; + #[tengri_proc::expose] + impl Test { + fn some_bool (&self) -> bool { + true + } + } + assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); + assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); + assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); + assert_eq!(Test.get(&Value::Num(0)), Some(false)); + assert_eq!(Test.get(&Value::Num(1)), Some(true)); +} diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 3869a8e..0d3ef75 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -43,6 +43,9 @@ handle!(TuiIn: |self: Example, input|{ #[tengri_proc::expose] impl Example { + fn _todo_u16_stub (&self) -> u16 { todo!() } + fn _todo_bool_stub (&self) -> bool { todo!() } + fn _todo_usize_stub (&self) -> usize { todo!() } //[bool] => {} //[u16] => {} //[usize] => {} From 8dda576c9da991f05a73c82f1df310063e0adc7a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 May 2025 15:25:09 +0300 Subject: [PATCH 054/178] add tengri_core; fix errors and warnings; unify deps --- Cargo.lock | 10 +++++ Cargo.toml | 36 +++++++++++---- core/Cargo.toml | 6 +++ core/src/lib.rs | 7 +++ dsl/Cargo.toml | 10 ++--- dsl/src/{dsl.rs => dsl_parse.rs} | 75 ++++++++------------------------ dsl/src/dsl_provide.rs | 44 +++++++++++++++++++ dsl/src/lib.rs | 19 +++++--- input/Cargo.toml | 7 +-- input/src/lib.rs | 17 +++----- output/Cargo.toml | 11 ++--- output/src/lib.rs | 7 +-- proc/Cargo.toml | 9 ++-- proc/src/proc_command.rs | 27 +----------- tengri/Cargo.toml | 17 ++++---- tengri/src/lib.rs | 1 + tui/Cargo.toml | 27 ++++++------ tui/src/lib.rs | 2 + 18 files changed, 180 insertions(+), 152 deletions(-) create mode 100644 core/Cargo.toml create mode 100644 core/src/lib.rs rename dsl/src/{dsl.rs => dsl_parse.rs} (81%) create mode 100644 dsl/src/dsl_provide.rs diff --git a/Cargo.lock b/Cargo.lock index 0ba0ef4..97bf463 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,6 +938,7 @@ version = "0.13.0" dependencies = [ "crossterm", "tengri", + "tengri_core", "tengri_dsl", "tengri_input", "tengri_output", @@ -945,6 +946,10 @@ dependencies = [ "tengri_tui", ] +[[package]] +name = "tengri_core" +version = "0.13.0" + [[package]] name = "tengri_dsl" version = "0.13.0" @@ -952,6 +957,7 @@ dependencies = [ "itertools 0.14.0", "konst", "proptest", + "tengri_core", "tengri_tui", "thiserror", ] @@ -960,6 +966,7 @@ dependencies = [ name = "tengri_input" version = "0.13.0" dependencies = [ + "tengri_core", "tengri_dsl", "tengri_tui", ] @@ -971,6 +978,7 @@ dependencies = [ "proptest", "proptest-derive", "tengri", + "tengri_core", "tengri_dsl", "tengri_tui", ] @@ -983,6 +991,7 @@ dependencies = [ "proc-macro2", "quote", "syn", + "tengri_core", ] [[package]] @@ -998,6 +1007,7 @@ dependencies = [ "rand", "ratatui", "tengri", + "tengri_core", "tengri_dsl", "tengri_input", "tengri_output", diff --git a/Cargo.toml b/Cargo.toml index 4a6d408..a7bb2eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,15 @@ -[workspace.package] -version = "0.13.0" -edition = "2024" +[profile.release] +lto = true + +[profile.coverage] +inherits = "test" +lto = false [workspace] resolver = "2" members = [ "./tengri", + "./core", "./input", "./output", "./tui", @@ -13,9 +17,25 @@ members = [ "./proc", ] -[profile.release] -lto = true +[workspace.package] +version = "0.13.0" +edition = "2024" -[profile.coverage] -inherits = "test" -lto = false +[workspace.dependencies] +atomic_float = { version = "1" } +better-panic = { version = "0.3.0" } +crossterm = { version = "0.28.1" } +heck = { version = "0.5" } +itertools = { version = "0.14.0" } +konst = { version = "0.3.16", features = [ "rust_1_83" ] } +palette = { version = "0.7.6", features = [ "random" ] } +proc-macro2 = { version = "1", features = ["span-locations"] } +proptest = { version = "^1" } +proptest-derive = { version = "^0.5.1" } +quanta = { version = "0.12.3" } +quote = { version = "1" } +rand = { version = "0.8.5" } +ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } +syn = { version = "2", features = ["full", "extra-traits"] } +thiserror = { version = "2.0" } +unicode-width = { version = "0.2" } diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..b4e9baa --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tengri_core" +description = "UI metaframework, core definitions." +version = { workspace = true } +edition = { workspace = true } + diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..22288d1 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,7 @@ +use std::error::Error; + +/// Standard result type. +pub type Usually = Result>; + +/// Standard optional result type. +pub type Perhaps = Result, Box>; diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 3609ada..ab0bc37 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -5,12 +5,10 @@ version = { workspace = true } edition = { workspace = true } [dependencies] -konst = { version = "0.3.16", features = [ "rust_1_83" ] } -itertools = "0.14.0" -thiserror = "2.0" - -[features] -default = [] +tengri_core = { path = "../core" } +konst = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] tengri_tui = { path = "../tui" } diff --git a/dsl/src/dsl.rs b/dsl/src/dsl_parse.rs similarity index 81% rename from dsl/src/dsl.rs rename to dsl/src/dsl_parse.rs index f4eedaf..3923cd4 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl_parse.rs @@ -1,5 +1,4 @@ use crate::*; -use thiserror::Error; pub type ParseResult = Result; @@ -16,41 +15,6 @@ pub type ParseResult = Result; Code(u8), } -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 - } -} - -/// 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 - } -} - -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() - } -} - /// Implement the const iterator pattern. #[macro_export] macro_rules! const_iter { ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { @@ -138,6 +102,7 @@ impl<'a> SourceIter<'a> { } } } + /// Static iteration helper. #[macro_export] macro_rules! iterate { ($expr:expr => $arg: pat => $body:expr) => { @@ -240,7 +205,9 @@ pub const fn to_digit (c: char) -> Result { } impl<'source> Token<'source> { - pub const fn new (source: &'source str, start: usize, length: usize, value: Value<'source>) -> Self { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: Value<'source> + ) -> Self { Self { source, start, length, value } } pub const fn end (&self) -> usize { @@ -248,7 +215,6 @@ impl<'source> Token<'source> { } pub const fn slice (&'source self) -> &'source str { self.slice_source(self.source) - //str_range(self.source, self.start, self.end()) } pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { str_range(source, self.start, self.end()) @@ -256,6 +222,9 @@ impl<'source> Token<'source> { pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { str_range(source, self.start.saturating_add(1), self.end()) } + pub const fn with_value (self, value: Value<'source>) -> Self { + Self { value, ..self } + } pub const fn value (&self) -> Value { self.value } @@ -272,49 +241,43 @@ impl<'source> Token<'source> { } } pub const fn grow_key (self) -> Self { - let mut token = self.grow(); - token.value = Key(token.slice_source(self.source)); - token + let token = self.grow(); + token.with_value(Key(token.slice_source(self.source))) } pub const fn grow_sym (self) -> Self { - let mut token = self.grow(); - token.value = Sym(token.slice_source(self.source)); - token + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.source))) } pub const fn grow_str (self) -> Self { - let mut token = self.grow(); - token.value = Str(token.slice_source(self.source)); - token + let token = self.grow(); + token.with_value(Str(token.slice_source(self.source))) } pub const fn grow_exp (self) -> Self { - let mut token = self.grow(); + let token = self.grow(); if let Exp(depth, _) = token.value { - token.value = Exp(depth, TokenIter::new(token.slice_source_exp(self.source))); + token.with_value(Exp(depth, TokenIter::new(token.slice_source_exp(self.source)))) } else { unreachable!() } - token } pub const fn grow_in (self) -> Self { - let mut token = self.grow_exp(); + let token = self.grow_exp(); if let Value::Exp(depth, source) = token.value { - token.value = Value::Exp(depth.saturating_add(1), source) + token.with_value(Value::Exp(depth.saturating_add(1), source)) } else { unreachable!() } - token } pub const fn grow_out (self) -> Self { - let mut token = self.grow_exp(); + let token = self.grow_exp(); if let Value::Exp(depth, source) = token.value { if depth > 0 { - token.value = Value::Exp(depth - 1, source) + token.with_value(Value::Exp(depth - 1, source)) } else { return self.error(Unexpected(')')) } } else { unreachable!() } - token } } diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs new file mode 100644 index 0000000..209b37c --- /dev/null +++ b/dsl/src/dsl_provide.rs @@ -0,0 +1,44 @@ +use crate::*; + +/// Map EDN tokens to parameters of a given type for a given context +pub trait Dsl: Sized { + fn take <'state, 'source> (_: &'state Self, _: &mut TokenIter<'source>) -> Perhaps { + Ok(None) + } +} + +/// 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 + } +} + +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 + } +} + diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index c685572..c72ff23 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,3 +1,7 @@ +#![feature(adt_const_params)] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_fn_trait_return)] + //! [Token]s are parsed substrings with an associated [Value]. //! //! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] @@ -31,17 +35,18 @@ //! assert_eq!(view.0.0, src); //! assert_eq!(view.peek(), view.0.peek()) //! ``` -#![feature(adt_const_params)] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_fn_trait_return)] -pub(crate) use self::Value::*; -pub(crate) use self::ParseError::*; +pub(crate) use ::tengri_core::*; + +pub(crate) use std::fmt::Debug; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; -pub(crate) use std::fmt::Debug; +pub(crate) use thiserror::Error; +pub(crate) use self::Value::*; +pub(crate) use self::ParseError::*; -mod dsl; pub use self::dsl::*; +mod dsl_parse; pub use self::dsl_parse::*; +mod dsl_provide; pub use self::dsl_provide::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/input/Cargo.toml b/input/Cargo.toml index 3b7772f..d3bf1ec 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -4,12 +4,13 @@ description = "UI metaframework, input layer." version = { workspace = true } edition = { workspace = true } -[dependencies] -tengri_dsl = { optional = true, path = "../dsl" } - [features] dsl = [ "tengri_dsl" ] +[dependencies] +tengri_core = { path = "../core" } +tengri_dsl = { optional = true, path = "../dsl" } + [dev-dependencies] tengri_tui = { path = "../tui" } tengri_dsl = { path = "../dsl" } diff --git a/input/src/lib.rs b/input/src/lib.rs index c4c230d..21035fe 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,18 +1,16 @@ #![feature(associated_type_defaults)] -pub(crate) use std::error::Error; -pub(crate) type Perhaps = Result, Box>; -#[cfg(test)] pub(crate) type Usually = Result>; -#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; -#[cfg(feature = "dsl")] mod input_dsl; -#[cfg(feature = "dsl")] pub use self::input_dsl::*; +pub(crate) use tengri_core::*; mod input_macros; mod input_command; pub use self::input_command::*; mod input_handle; pub use self::input_handle::*; -#[cfg(test)] -#[test] fn test_stub_input () -> Usually<()> { +#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; +#[cfg(feature = "dsl")] mod input_dsl; +#[cfg(feature = "dsl")] pub use self::input_dsl::*; + +#[cfg(test)] #[test] fn test_stub_input () -> Usually<()> { use crate::*; struct TestInput(bool); enum TestEvent { Test1 } @@ -33,8 +31,7 @@ mod input_handle; pub use self::input_handle::*; Ok(()) } -#[cfg(all(test, feature = "dsl"))] -#[test] fn test_dsl_keymap () -> Usually<()> { +#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { let keymap = SourceIter::new(""); Ok(()) } diff --git a/output/Cargo.toml b/output/Cargo.toml index 2505df5..5172710 100644 --- a/output/Cargo.toml +++ b/output/Cargo.toml @@ -4,15 +4,16 @@ description = "UI metaframework, output layer." version = { workspace = true } edition = { workspace = true } -[dependencies] -tengri_dsl = { optional = true, path = "../dsl" } - [features] dsl = [ "tengri_dsl" ] +[dependencies] +tengri_core = { path = "../core" } +tengri_dsl = { optional = true, path = "../dsl" } + [dev-dependencies] tengri = { path = "../tengri", features = [ "dsl", "tui" ] } tengri_tui = { path = "../tui" } tengri_dsl = { path = "../dsl" } -proptest = "^1" -proptest-derive = "^0.5.1" +proptest = { workspace = true } +proptest-derive = { workspace = true } diff --git a/output/src/lib.rs b/output/src/lib.rs index 45bf90a..2b12b04 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -7,18 +7,13 @@ mod space; pub use self::space::*; mod ops; pub use self::ops::*; mod output; pub use self::output::*; +pub(crate) use tengri_core::*; pub(crate) use std::marker::PhantomData; -pub(crate) use std::error::Error; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; #[cfg(feature = "dsl")] mod view; #[cfg(feature = "dsl")] pub use self::view::*; -/// Standard result type. -pub type Usually = Result>; -/// Standard optional result type. -pub type Perhaps = Result, Box>; - #[cfg(test)] use proptest_derive::Arbitrary; #[cfg(test)] #[test] fn test_stub_output () -> Usually<()> { diff --git a/proc/Cargo.toml b/proc/Cargo.toml index 6001947..d7c3a9a 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -8,7 +8,8 @@ edition = { workspace = true } proc-macro = true [dependencies] -syn = { version = "2", features = ["full", "extra-traits"] } -quote = { version = "1" } -proc-macro2 = { version = "1", features = ["span-locations"] } -heck = { version = "0.5" } +tengri_core = { path = "../core" } +syn = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } +heck = { workspace = true } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index cc5b134..502041f 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -10,10 +10,7 @@ pub(crate) struct CommandMeta(Ident); pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); #[derive(Debug, Clone)] -struct CommandArm(Ident, Vec, ReturnType); - -#[derive(Debug, Clone)] -struct CommandVariant(Ident, Vec); +struct CommandArm(Ident, Vec, #[allow(unused)] ReturnType); impl Parse for CommandMeta { fn parse (input: ParseStream) -> Result { @@ -203,25 +200,3 @@ impl CommandArm { write_quote(quote! { Self::#variant => Self::#ident(state, #(#give_args)* #give_rest), }) } } - -impl ToTokens for CommandVariant { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self(ident, args) = self; - out.append(LitStr::new(&format!("{}", ident), Span::call_site()) - .token()); - out.append(Group::new(Delimiter::Parenthesis, { - let mut out = TokenStream2::new(); - for arg in args.iter() { - if let FnArg::Typed(PatType { ty, .. }) = arg { - out.append(LitStr::new( - &format!("{}", quote! { #ty }), - Span::call_site() - ).token()); - out.append(Punct::new(',', Alone)); - } - } - out - })); - out.append(Punct::new(',', Alone)); - } -} diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index 23e87f8..cd87dfc 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -4,7 +4,15 @@ edition = "2024" description = "UI metaframework." version = { workspace = true } +[features] +default = [ "input", "output", "tui" ] +input = [ "tengri_input" ] +output = [ "tengri_output" ] +tui = [ "tengri_tui" ] +dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ] + [dependencies] +tengri_core = { path = "../core" } tengri_dsl = { optional = true, path = "../dsl" } tengri_input = { optional = true, path = "../input" } tengri_output = { optional = true, path = "../output" } @@ -13,11 +21,4 @@ tengri_tui = { optional = true, path = "../tui" } [dev-dependencies] tengri_proc = { path = "../proc" } tengri = { path = ".", features = [ "dsl" ] } -crossterm = "0.28.1" - -[features] -default = [ "input", "output", "tui" ] -input = [ "tengri_input" ] -output = [ "tengri_output" ] -tui = [ "tengri_tui" ] -dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl", "tengri_tui/dsl" ] +crossterm = { workspace = true } diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 53850cd..5fd2870 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -1,3 +1,4 @@ +pub use ::tengri_core::*; #[cfg(feature="output")] pub use ::tengri_output as output; #[cfg(feature="input")] pub use ::tengri_input as input; #[cfg(feature="dsl")] pub use ::tengri_dsl as dsl; diff --git a/tui/Cargo.toml b/tui/Cargo.toml index a9eb4d9..5fe3f34 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -4,25 +4,26 @@ description = "UI metaframework, Ratatui backend." version = { workspace = true } edition = { workspace = true } -[dependencies] -palette = { version = "0.7.6", features = [ "random" ] } -rand = "0.8.5" -crossterm = "0.28.1" -ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } -better-panic = "0.3.0" -konst = { version = "0.3.16", features = [ "rust_1_83" ] } -atomic_float = "1" -quanta = "0.12.3" -unicode-width = "0.2" +[features] +dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ] +[dependencies] +tengri_core = { path = "../core" } tengri_input = { path = "../input" } tengri_output = { path = "../output" } tengri_dsl = { optional = true, path = "../dsl" } +palette = { workspace = true } +rand = { workspace = true } +crossterm = { workspace = true } +ratatui = { workspace = true } +better-panic = { workspace = true } +konst = { workspace = true } +atomic_float = { workspace = true } +quanta = { workspace = true } +unicode-width = { workspace = true } + [dev-dependencies] tengri = { path = "../tengri", features = [ "dsl" ] } tengri_dsl = { path = "../dsl" } tengri_proc = { path = "../proc" } - -[features] -dsl = [ "tengri_dsl", "tengri_input/dsl", "tengri_output/dsl" ] diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 8e98c58..01eb7d9 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -3,6 +3,8 @@ mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; +pub(crate) use ::tengri_core::*; + pub use ::tengri_input as input; pub(crate) use ::tengri_input::*; From f18e01c220e978ef2d011cc4c7c6de4a6462b792 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 May 2025 15:28:42 +0300 Subject: [PATCH 055/178] partially fix tests and examples --- tengri/src/lib.rs | 36 ++++++++++++++++++------------------ tui/examples/tui.rs | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 5fd2870..25d7b27 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -5,8 +5,7 @@ pub use ::tengri_core::*; #[cfg(feature="tui")] pub use ::tengri_tui as tui; #[cfg(test)] extern crate tengri_proc; -#[cfg(test)] #[test] fn test_subcommand () -> crate::output::Usually<()> { - use crate::output::Perhaps; +#[cfg(test)] #[test] fn test_subcommand () -> Usually<()> { use crate::input::{Command, InputMap, KeyMap, Handle, handle}; use crate::dsl::{TryFromDsl, TokenIter}; use crate::tui::TuiIn; @@ -81,20 +80,21 @@ pub use ::tengri_core::*; Ok(()) } -#[cfg(test)] #[test] fn test_dsl_context () { - use crate::dsl::Value; +//FIXME: +//#[cfg(test)] #[test] fn test_dsl_context () { + //use crate::dsl::{Context, Value}; - struct Test; - #[tengri_proc::expose] - impl Test { - fn some_bool (&self) -> bool { - true - } - } - assert_eq!(Test.get(&Value::Sym(":false")), Some(false)); - assert_eq!(Test.get(&Value::Sym(":true")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":some-bool")), Some(true)); - assert_eq!(Test.get(&Value::Sym(":missing-bool")), None); - assert_eq!(Test.get(&Value::Num(0)), Some(false)); - assert_eq!(Test.get(&Value::Num(1)), Some(true)); -} + //struct Test; + //#[tengri_proc::expose] + //impl Test { + //fn some_bool (&self) -> bool { + //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/examples/tui.rs b/tui/examples/tui.rs index 0d3ef75..066ab20 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -1,4 +1,4 @@ -use tengri::{self, input::*, output::*, tui::*, dsl::*}; +use tengri::{self, Usually, Perhaps, input::*, output::*, tui::*, dsl::*}; use std::sync::{Arc, RwLock}; use crossterm::event::{*, KeyCode::*}; use crate::ratatui::style::Color; @@ -70,7 +70,7 @@ view!(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, SourceIter::new(src))); + let content = Tui::bg(Color::Rgb(10, 10, 60), View(self, TokenIter::new(src))); self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content))) }; { ":title" => Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed(), From ed772b9872572c39dc4afef61729a2ee068ec3a0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 May 2025 15:49:33 +0300 Subject: [PATCH 056/178] dsl: extract dsl_error; ParseError -> DslError --- dsl/src/dsl_error.rs | 16 ++++++++++++++++ dsl/src/dsl_parse.rs | 23 ++++------------------- dsl/src/dsl_provide.rs | 11 +++++++++++ dsl/src/lib.rs | 5 +++-- 4 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 dsl/src/dsl_error.rs diff --git a/dsl/src/dsl_error.rs b/dsl/src/dsl_error.rs new file mode 100644 index 0000000..ffb53fd --- /dev/null +++ b/dsl/src/dsl_error.rs @@ -0,0 +1,16 @@ +use crate::*; + +pub type DslResult = Result; + +#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslError { + #[error("parse failed: not implemented")] + Unimplemented, + #[error("parse failed: empty")] + Empty, + #[error("parse failed: incomplete")] + Incomplete, + #[error("parse failed: unexpected character '{0}'")] + Unexpected(char), + #[error("parse failed: error #{0}")] + Code(u8), +} diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs index 3923cd4..ee06792 100644 --- a/dsl/src/dsl_parse.rs +++ b/dsl/src/dsl_parse.rs @@ -1,20 +1,5 @@ use crate::*; -pub type ParseResult = Result; - -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum ParseError { - #[error("parse failed: not implemented")] - Unimplemented, - #[error("parse failed: empty")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), -} - /// Implement the const iterator pattern. #[macro_export] macro_rules! const_iter { ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { @@ -170,7 +155,7 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { } } -pub const fn to_number (digits: &str) -> Result { +pub const fn to_number (digits: &str) -> DslResult { let mut value = 0; iterate!(char_indices(digits) => (_, c) => match to_digit(c) { Ok(digit) => value = 10 * value + digit, @@ -179,7 +164,7 @@ pub const fn to_number (digits: &str) -> Result { Ok(value) } -pub const fn to_digit (c: char) -> Result { +pub const fn to_digit (c: char) -> DslResult { Ok(match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, @@ -196,7 +181,7 @@ pub const fn to_digit (c: char) -> Result { #[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { #[default] Nil, - Err(ParseError), + Err(DslError), Num(usize), Sym(&'source str), Key(&'source str), @@ -228,7 +213,7 @@ impl<'source> Token<'source> { pub const fn value (&self) -> Value { self.value } - pub const fn error (self, error: ParseError) -> Self { + pub const fn error (self, error: DslError) -> Self { Self { value: Value::Err(error), ..self } } pub const fn grow (self) -> Self { diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 209b37c..c22e5d6 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,10 +1,21 @@ use crate::*; /// 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: &'state Self, iter: &mut TokenIter<'source> + ) -> Usually { + if let Some(value) = Self::take(state, iter)? { + Ok(value) + } else { + Result::Err("not found".into()) // TODO add info and error type + } + } } /// Map EDN tokens to parameters of a given type for a given context diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index c72ff23..9c9b44e 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -43,8 +43,9 @@ 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::Value::*; -pub(crate) use self::ParseError::*; +pub(crate) use self::DslError::*; +mod dsl_error; pub use self::dsl_error::*; mod dsl_parse; pub use self::dsl_parse::*; mod dsl_provide; pub use self::dsl_provide::*; @@ -148,7 +149,7 @@ mod dsl_provide; pub use self::dsl_provide::*; Ok(()) } -//#[cfg(test)] #[test] fn test_examples () -> Result<(), ParseError> { +//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { //// 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. From 18687365975c008e774707a3f42a7836794839db Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 May 2025 15:49:46 +0300 Subject: [PATCH 057/178] proc: log parse info on give_err --- proc/src/proc_command.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 502041f..bfe9246 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -70,7 +70,8 @@ impl ToTokens for CommandDef { state: &'state #target, iter: &mut ::tengri::dsl::TokenIter<'source> ) -> Option { let mut iter = iter.clone(); - match iter.next() { + let token = iter.next(); + match token { #(#matchers)* _ => None } @@ -154,10 +155,12 @@ impl CommandArm { 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 = LitStr::new(&format!("{}: missing value for \"{}\" ({})", - 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).expect(#give_err), + #arg: ::tengri::dsl::Context::get(state, &mut iter) + .unwrap_or_else(||panic!(#give_err, token)), }); } out From faecc2c304ad2c0ebd78d21170a02c172fd356bf Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 10 May 2025 15:50:07 +0300 Subject: [PATCH 058/178] update dependencies --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97bf463..6c5c30d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,9 +46,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -834,9 +834,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -926,7 +926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix 1.0.7", "windows-sys 0.59.0", From 632977a0dc4c01d3a291d3f595d7f7fe911d748b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 13 May 2025 20:25:44 +0300 Subject: [PATCH 059/178] dsl: implement Display for Value --- dsl/src/dsl_parse.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs index ee06792..d81c54b 100644 --- a/dsl/src/dsl_parse.rs +++ b/dsl/src/dsl_parse.rs @@ -189,6 +189,21 @@ pub const fn to_digit (c: char) -> DslResult { Exp(usize, TokenIter<'source>), } +impl<'source> std::fmt::Display for Value<'source> { + fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(_, e) => format!("{e:?}"), + }); + Ok(()) + } +} + impl<'source> Token<'source> { pub const fn new ( source: &'source str, start: usize, length: usize, value: Value<'source> From b45ac8f417b2f4e83e116a9ee5fe4bf3ad57a726 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 13 May 2025 20:25:55 +0300 Subject: [PATCH 060/178] tui: remove FileBrowser --- tui/src/tui_content/tui_file.rs | 87 --------------------------------- 1 file changed, 87 deletions(-) diff --git a/tui/src/tui_content/tui_file.rs b/tui/src/tui_content/tui_file.rs index 401b8ae..e69de29 100644 --- a/tui/src/tui_content/tui_file.rs +++ b/tui/src/tui_content/tui_file.rs @@ -1,87 +0,0 @@ -use crate::*; -/// Browses for phrase to import/export -#[derive(Debug, Clone)] -pub struct FileBrowser { - pub cwd: PathBuf, - pub dirs: Vec<(OsString, String)>, - pub files: Vec<(OsString, String)>, - pub filter: String, - pub index: usize, - pub scroll: usize, - pub size: Measure -} -/// Commands supported by [FileBrowser] -#[derive(Debug, Clone, PartialEq)] -pub enum FileBrowserCommand { - Begin, - Cancel, - Confirm, - Select(usize), - Chdir(PathBuf), - Filter(Arc), -} -content!(TuiOut: |self: FileBrowser| /*Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - add(&format!("{}/{i}", self.index))?; - Ok(()) -})*/"todo"); -impl FileBrowser { - pub fn new (cwd: Option) -> Usually { - let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; - let mut dirs = vec![]; - let mut files = vec![]; - for entry in std::fs::read_dir(&cwd)? { - let entry = entry?; - let name = entry.file_name(); - let decoded = name.clone().into_string().unwrap_or_else(|_|"".to_string()); - let meta = entry.metadata()?; - if meta.is_dir() { - dirs.push((name, format!("📁 {decoded}"))); - } else if meta.is_file() { - files.push((name, format!("📄 {decoded}"))); - } - } - Ok(Self { - cwd, - dirs, - files, - filter: "".to_string(), - index: 0, - scroll: 0, - size: Measure::new(), - }) - } - pub fn len (&self) -> usize { - self.dirs.len() + self.files.len() - } - pub fn is_dir (&self) -> bool { - self.index < self.dirs.len() - } - pub fn is_file (&self) -> bool { - self.index >= self.dirs.len() - } - pub fn path (&self) -> PathBuf { - self.cwd.join(if self.is_dir() { - &self.dirs[self.index].0 - } else if self.is_file() { - &self.files[self.index - self.dirs.len()].0 - } else { - unreachable!() - }) - } - pub fn chdir (&self) -> Usually { - Self::new(Some(self.path())) - } -} From 663efede640135fbb3182b586ea193b34ffd72f0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 13 May 2025 20:27:12 +0300 Subject: [PATCH 061/178] dsl: don't eat error --- dsl/src/dsl_parse.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs index d81c54b..81a0336 100644 --- a/dsl/src/dsl_parse.rs +++ b/dsl/src/dsl_parse.rs @@ -199,8 +199,7 @@ impl<'source> std::fmt::Display for Value<'source> { Key(s) => format!("{s}"), Str(s) => format!("{s}"), Exp(_, e) => format!("{e:?}"), - }); - Ok(()) + }) } } From 5d546affed5c643e5812000297321ee60fce8fde Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 13 May 2025 20:27:41 +0300 Subject: [PATCH 062/178] tui: remove tui_file --- tui/src/tui_content.rs | 1 - tui/src/tui_content/tui_file.rs | 0 2 files changed, 1 deletion(-) delete mode 100644 tui/src/tui_content/tui_file.rs diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 0ea658c..6b668c5 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -24,7 +24,6 @@ impl> Content for std::sync::Arc { mod tui_border; pub use self::tui_border::*; mod tui_color; pub use self::tui_color::*; mod tui_field; pub use self::tui_field::*; -mod tui_file; pub use self::tui_file::*; mod tui_phat; pub use self::tui_phat::*; mod tui_repeat; pub use self::tui_repeat::*; mod tui_scroll; pub use self::tui_scroll::*; diff --git a/tui/src/tui_content/tui_file.rs b/tui/src/tui_content/tui_file.rs deleted file mode 100644 index e69de29..0000000 From bad20f5037dc22d572a8381840fab871ce65f565 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 00:46:09 +0300 Subject: [PATCH 063/178] tui: add button_2, button_3 --- tui/src/tui_content.rs | 1 + tui/src/tui_content/tui_button.rs | 40 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tui/src/tui_content/tui_button.rs diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 6b668c5..38960b9 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -22,6 +22,7 @@ impl> Content for std::sync::Arc { } mod tui_border; pub use self::tui_border::*; +mod tui_button; pub use self::tui_button::*; mod tui_color; pub use self::tui_color::*; mod tui_field; pub use self::tui_field::*; mod tui_phat; pub use self::tui_phat::*; diff --git a/tui/src/tui_content/tui_button.rs b/tui/src/tui_content/tui_button.rs new file mode 100644 index 0000000..2a297da --- /dev/null +++ b/tui/src/tui_content/tui_button.rs @@ -0,0 +1,40 @@ +use crate::{*, Color::*}; + +pub fn button_2 <'a> ( + key: impl Content + 'a, label: impl Content + 'a, editing: bool, +) -> impl Content + 'a { + let key = Tui::fg_bg(Tui::g(0), Tui::orange(), Bsp::e( + Tui::fg_bg(Tui::orange(), Reset, "▐"), + Bsp::e(key, Tui::fg(Tui::g(96), "▐")) + )); + let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)); + Tui::bold(true, Bsp::e(key, label)) +} + +pub fn button_3 <'a, K, L, V> ( + key: K, + label: L, + value: V, + editing: bool, +) -> impl Content + 'a where + K: Content + 'a, + L: Content + 'a, + V: Content + 'a, +{ + let key = Tui::fg_bg(Tui::g(0), Tui::orange(), + Bsp::e(Tui::fg_bg(Tui::orange(), Reset, "▐"), Bsp::e(key, Tui::fg(if editing { + Tui::g(128) + } else { + Tui::g(96) + }, "▐")))); + let label = Bsp::e( + When::new(!editing, Bsp::e( + Tui::fg_bg(Tui::g(255), Tui::g(96), label), + Tui::fg_bg(Tui::g(128), Tui::g(96), "▐"), + )), + Bsp::e( + Tui::fg_bg(Tui::g(224), Tui::g(128), value), + Tui::fg_bg(Tui::g(128), Reset, "▌"), + )); + Tui::bold(true, Bsp::e(key, label)) +} From a9619ab9cea40aed3ec30483f4a7db03ca7fec9d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 14:35:38 +0300 Subject: [PATCH 064/178] fix(proc): use TryFromDsl locally --- core/src/lib.rs | 9 +++++++++ proc/src/proc_command.rs | 1 + tui/src/lib.rs | 10 ---------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 22288d1..18c672b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -5,3 +5,12 @@ pub type Usually = Result>; /// Standard optional result type. pub type Perhaps = Result, Box>; + +/// Implement the `From` trait. +#[macro_export] macro_rules! from { + ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { + impl $(<$($lt),+>)? From<$Source> for $Target { + fn from ($state:$Source) -> Self { $cb } + } + }; +} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index bfe9246..f8e9d2a 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -82,6 +82,7 @@ impl ToTokens for CommandDef { fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) -> Option<#enumeration> { + use ::tengri::dsl::TryFromDsl; #enumeration::try_from_expr(self, iter) } } diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 01eb7d9..79648b9 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -33,16 +33,6 @@ pub use ::ratatui; pub(crate) use ratatui::{ pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; pub(crate) use std::io::{stdout, Stdout}; -pub(crate) use std::path::PathBuf; -pub(crate) use std::ffi::OsString; - -#[macro_export] macro_rules! from { - ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { - impl $(<$($lt),+>)? From<$Source> for $Target { - fn from ($state:$Source) -> Self { $cb } - } - }; -} #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { use crate::*; From 8bfd1a23a1f880a1d2fb104a158fc51f244acd6e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 17:53:34 +0300 Subject: [PATCH 065/178] core, output: add Has, HasSize --- core/src/lib.rs | 5 +++++ output/src/space/measure.rs | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 18c672b..f3cf313 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,3 +14,8 @@ pub type Perhaps = Result, Box>; } }; } + +pub trait Has: Send + Sync { + fn get (&self) -> &T; + fn get_mut (&mut self) -> &mut T; +} diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs index 2a1e415..9ea4765 100644 --- a/output/src/space/measure.rs +++ b/output/src/space/measure.rs @@ -3,13 +3,17 @@ use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub trait HasSize { fn size (&self) -> &Measure; + fn width (&self) -> usize { + self.size().w() + } + fn height (&self) -> usize { + self.size().w() + } } -#[macro_export] macro_rules! has_size { - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { - fn size (&$self) -> &Measure<$E> { $cb } - } +impl>> HasSize for T { + fn size (&self) -> &Measure { + self.get() } } From 496a9202d5bc4df70d31cb726227258760ed208d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 22:16:52 +0300 Subject: [PATCH 066/178] input: better dsl error handling --- input/src/input_dsl.rs | 10 +++++----- output/src/view.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index cde5a15..4575e24 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -51,19 +51,19 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f 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() { + let mut e = exp_iter.clone(); + match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, &mut exp_iter) { + if let Some(command) = C::try_from_expr(state, &mut e) { return Some(command) } } }, - _ => panic!("invalid config (expected symbol)") + _ => panic!("invalid config (expected symbol, got: {exp_iter:?})") } }, - _ => panic!("invalid config (expected expression)") + _ => panic!("invalid config (expected expression, got: {next:?})") } } None diff --git a/output/src/view.rs b/output/src/view.rs index f2e9ef4..484194d 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -54,7 +54,7 @@ pub trait ViewContext<'state, E: Output + 'state>: Send + Sync match value { Value::Sym(_) => self.get_content_sym(value), Value::Exp(_, _) => self.get_content_exp(value), - _ => panic!("only :symbols and (expressions) accepted here") + _ => panic!("only :symbols and (expressions) accepted here, got: {value:?}") } } fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) From 4c039c999bd9cff6a706c75161863215aeef5047 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 22:17:07 +0300 Subject: [PATCH 067/178] output: add memory of Stack primitive --- output/src/ops/stack.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 output/src/ops/stack.rs diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs new file mode 100644 index 0000000..ba8b949 --- /dev/null +++ b/output/src/ops/stack.rs @@ -0,0 +1,18 @@ +/*Stack::down(|add|{ + let mut i = 0; + for (_, name) in self.dirs.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + for (_, name) in self.files.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + add(&format!("{}/{i}", self.index))?; + Ok(()) +}));*/ + From f7306de55faea42bb1531955a92220fd48d9aadb Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 22:17:27 +0300 Subject: [PATCH 068/178] field: align west --- tui/src/tui_content/tui_field.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index 7c83950..fa3f123 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -17,10 +17,13 @@ pub struct FieldV(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldV { fn content (&self) -> impl Render { let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; - let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); - let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); - let title = Tui::bg(dark.rgb, Tui::fg(lighter.rgb, Tui::bold(true, title))); + let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); + let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); + let title = Tui::bg(dark.rgb, Tui::fg(lighter.rgb, Tui::bold(true, title))); let value = Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, value)); - Bsp::e(Bsp::s(row!(sep1, title, sep2), value), " ") + Bsp::n( + Align::w(value), + Fill::x(Align::w(row!(sep1, title, sep2))) + ) } } From c954965ae125136286291e5f0d4532edf98f46ad Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 14 May 2025 22:37:43 +0300 Subject: [PATCH 069/178] measure: w -> h --- output/src/space/measure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs index 9ea4765..a894b60 100644 --- a/output/src/space/measure.rs +++ b/output/src/space/measure.rs @@ -7,7 +7,7 @@ pub trait HasSize { self.size().w() } fn height (&self) -> usize { - self.size().w() + self.size().h() } } From 4ff4ea81735548f808302c61b619ad7804e1eec0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 15 May 2025 23:17:19 +0300 Subject: [PATCH 070/178] wip: field --- tui/src/tui_content/tui_field.rs | 84 ++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index fa3f123..3823182 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -5,10 +5,10 @@ impl, U: Content> Content for FieldH { fn content (&self) -> impl Render { let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; row!( - Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), - Tui::fg_bg(lighter.rgb, dark.rgb, Tui::bold(true, title)), - Tui::fg_bg(dark.rgb, darkest.rgb, "▌"), - Tui::fg_bg(lightest.rgb, darkest.rgb, value), + Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), + Tui::fg_bg(lightest.rgb, dark.rgb, title), + Tui::fg_bg(dark.rgb, darkest.rgb, "▌"), + Tui::fg_bg(lightest.rgb, darkest.rgb, Tui::bold(true, value)), ) } } @@ -17,13 +17,77 @@ pub struct FieldV(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldV { fn content (&self) -> impl Render { let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; - let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")); - let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")); - let title = Tui::bg(dark.rgb, Tui::fg(lighter.rgb, Tui::bold(true, title))); - let value = Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, value)); Bsp::n( - Align::w(value), - Fill::x(Align::w(row!(sep1, title, sep2))) + Align::w(Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, Tui::bold(true, value)))), + Fill::x(Align::w(row!( + Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▐")), + Tui::bg(dark.rgb, Tui::fg(lightest.rgb, title)), + Tui::bg(darkest.rgb, Tui::fg(dark.rgb, "▌")), + ))) ) } } + +// TODO: +pub struct Field { + pub direction: Direction, + pub label: Option, + pub label_fg: Option, + pub label_bg: Option, + pub label_align: Option, + pub value: Option, + pub value_fg: Option, + pub value_bg: Option, + pub value_align: Option, +} +impl, U: Content> Content for Field { + fn content (&self) -> impl Render { + "TODO" + } +} + +impl Field { + pub fn new (direction: Direction) -> Field<(), ()> { + Field { + direction, + label: None, + label_fg: None, + label_bg: None, + label_align: None, + value: None, + value_fg: None, + value_bg: None, + value_align: None, + } + } + fn label ( + self, + label: Option, + align: Option, + fg: Option, + bg: Option + ) -> Field { + Field { + label, + label_fg: fg, + label_bg: bg, + label_align: align, + ..self + } + } + fn value ( + self, + value: Option, + align: Option, + fg: Option, + bg: Option + ) -> Field { + Field { + value, + value_fg: fg, + value_bg: bg, + value_align: align, + ..self + } + } +} From b25977d878713f3ab89ed12b910eed67a1f88524 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 05:47:51 +0300 Subject: [PATCH 071/178] add has!, MaybeHas, maybe_has! --- core/src/lib.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index f3cf313..b512ae9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -16,6 +16,29 @@ pub type Perhaps = Result, Box>; } pub trait Has: Send + Sync { - fn get (&self) -> &T; + fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; } + +#[macro_export] macro_rules! has { + ($T:ty: |$self:ident : $S:ty| $x:expr) => { + impl Has<$T> for $S { + fn get (&$self) -> &$T { &$x } + fn get_mut (&mut $self) -> &mut $T { &mut $x } + } + }; +} + +pub trait MaybeHas: Send + Sync { + fn get (&self) -> Option<&T>; + fn get_mut (&mut self) -> Option<&mut T>; +} + +#[macro_export] macro_rules! maybe_has { + ($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => { + impl MaybeHas<$T> for $S { + fn get (&$self) -> Option<&$T> $x + fn get_mut (&mut $self) -> Option<&mut $T> $y + } + }; +} From a55e84c29f51606e0996f7f88b7664ca0d37365b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 10:15:07 +0300 Subject: [PATCH 072/178] output, tui: Stack implementation --- output/src/ops.rs | 1 + output/src/ops/stack.rs | 82 ++++++++++++++++++++++++++++++++ output/src/output.rs | 2 +- tui/src/tui_engine/tui_output.rs | 2 +- 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/output/src/ops.rs b/output/src/ops.rs index 2cd1784..4c6ea2a 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -2,6 +2,7 @@ mod align; pub use self::align::*; mod bsp; pub use self::bsp::*; mod cond; pub use self::cond::*; mod map; pub use self::map::*; +mod stack; pub use self::stack::*; //mod reduce; pub use self::reduce::*; mod thunk; pub use self::thunk::*; mod transform; pub use self::transform::*; diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs index ba8b949..816713c 100644 --- a/output/src/ops/stack.rs +++ b/output/src/ops/stack.rs @@ -1,3 +1,85 @@ +use crate::*; + +pub struct Stack { + __: PhantomData, + direction: Direction, + callback: F +} +impl Stack { + pub fn new (direction: Direction, callback: F) -> Self { + Self { direction, callback, __: Default::default(), } + } + pub fn north (callback: F) -> Self { + Self::new(Direction::North, callback) + } + pub fn south (callback: F) -> Self { + Self::new(Direction::South, callback) + } + pub fn east (callback: F) -> Self { + Self::new(Direction::East, callback) + } + pub fn west (callback: F) -> Self { + Self::new(Direction::West, callback) + } +} +impl)->())->()> Content for Stack { + fn layout (&self, mut to: E::Area) -> E::Area { + let mut x = to.x(); + let mut y = to.y(); + let mut w = to.w(); + let mut h = to.h(); + (self.callback)(&mut move |component: &dyn Render|{ + let layout = component.layout([x, y, w, h].into()); + match self.direction { + Direction::North => { + todo!() + }, + Direction::South => { + y = y + layout.h(); + h = h.minus(layout.h()); + }, + Direction::East => { + x = x + layout.w(); + w = w.minus(layout.w()); + }, + Direction::West => { + todo!() + }, + _ => unreachable!(), + } + }); + to + } + fn render (&self, to: &mut E) { + let mut x = to.x(); + let mut y = to.y(); + let mut w = to.w(); + let mut h = to.h(); + (self.callback)(&mut move |component: &dyn Render|{ + let layout = component.layout([x, y, w, h].into()); + match self.direction { + Direction::North => { + todo!() + }, + Direction::South => { + y = y + layout.h(); + h = h.minus(layout.h()); + to.place(layout, component); + }, + Direction::East => { + x = x + layout.w(); + w = w.minus(layout.w()); + to.place(layout, component); + }, + Direction::West => { + todo!() + }, + _ => unreachable!() + } + }); + } +} + /*Stack::down(|add|{ let mut i = 0; for (_, name) in self.dirs.iter() { diff --git a/output/src/output.rs b/output/src/output.rs index 98e3659..b5cc1fc 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -14,7 +14,7 @@ pub trait Output: Send + Sync + Sized { /// Mutable pointer to area fn area_mut (&mut self) -> &mut Self::Area; /// Render widget in area - fn place (&mut self, area: Self::Area, content: &impl Render); + fn place + ?Sized> (&mut self, area: Self::Area, content: &T); #[inline] fn x (&self) -> Self::Unit { self.area().x() } #[inline] fn y (&self) -> Self::Unit { self.area().y() } #[inline] fn w (&self) -> Self::Unit { self.area().w() } diff --git a/tui/src/tui_engine/tui_output.rs b/tui/src/tui_engine/tui_output.rs index 161f146..33b46ff 100644 --- a/tui/src/tui_engine/tui_output.rs +++ b/tui/src/tui_engine/tui_output.rs @@ -12,7 +12,7 @@ impl Output for TuiOut { type Area = [Self::Unit;4]; #[inline] fn area (&self) -> [u16;4] { self.area } #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } - #[inline] fn place (&mut self, area: [u16;4], content: &impl Render) { + #[inline] fn place + ?Sized> (&mut self, area: [u16;4], content: &T) { let last = self.area(); *self.area_mut() = area; content.render(self); From c9f01648712a0c3c1a8290fc09c65746decbbbcb Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 17:59:22 +0300 Subject: [PATCH 073/178] tui: add cb/br open/close --- tui/src/tui_engine/tui_keys.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tui/src/tui_engine/tui_keys.rs b/tui/src/tui_engine/tui_keys.rs index 6e0e0e6..cda5899 100644 --- a/tui/src/tui_engine/tui_keys.rs +++ b/tui/src/tui_engine/tui_keys.rs @@ -68,8 +68,10 @@ impl KeyMatcher { "backtick" => Char('`'), "lt" => Char('<'), "gt" => Char('>'), - "openbracket" => Char('['), - "closebracket" => Char(']'), + "cbopen" | "openbrace" => Char('{'), + "cbclose" | "closebrace" => Char('}'), + "bropen" | "openbracket" => Char('['), + "brclose" | "closebracket" => Char(']'), "pgup" | "pageup" => PageUp, "pgdn" | "pagedown" => PageDown, "f1" => F(1), From b1275265702a835d8cf69fbee2ddee1915f6024b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 19:27:04 +0300 Subject: [PATCH 074/178] fix(output): fix cond --- output/src/ops/cond.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 5e71bef..902a2dc 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -36,15 +36,13 @@ try_from_expr!(<'source, 'state, E>: When>: |state, iter| { try_from_expr!(<'source, 'state, E>: Either, RenderBox<'state, E>>: |state, iter| { if let Some(Token { value: Value::Key("either"), .. }) = iter.peek() { let _ = iter.next().unwrap(); - let content = iter.next().expect("no content specified").value; - let alternate = iter.next().expect("no alternate specified").value; + //panic!("{iter:?}"); return Some(Self( - state.get(&mut iter) - .expect("no condition provided"), - state.get_content(&content) - .unwrap_or_else(||panic!("no content 1 corresponding to {:?}", &content)), - state.get_content(&alternate) - .unwrap_or_else(||panic!("no content 2 corresponding to {:?}", &alternate)), + state.get(&mut iter).expect("no condition provided"), + state.get_content(&iter.next().expect("no content specified").value) + .unwrap_or_else(||panic!("no content 1: {iter:?}")), + state.get_content(&iter.next().expect("no alternate specified").value) + .unwrap_or_else(||panic!("no content 2: {iter:?}")), )) } }); From 12998a94ea02bc84c1a490783bc76b10789ce37f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 20:07:57 +0300 Subject: [PATCH 075/178] output: report more info on error from bsp --- output/src/ops/bsp.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index fe48efc..300d244 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -25,9 +25,10 @@ try_from_expr!(<'source, 'state, E>: Bsp, RenderBox<'state, 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 _ = iter.next().unwrap(); - let c1 = iter.next().expect("no content1 specified"); - let c2 = iter.next().expect("no content2 specified"); + 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 { From f21781e81664e1991e3985e2377becca9c1d58cf Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 20:57:51 +0300 Subject: [PATCH 076/178] tui: adjust color balance --- tui/src/tui_content/tui_color.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tui/src/tui_content/tui_color.rs b/tui/src/tui_content/tui_color.rs index 3a8adf3..375694b 100644 --- a/tui/src/tui_content/tui_color.rs +++ b/tui/src/tui_content/tui_color.rs @@ -103,9 +103,9 @@ impl ItemTheme { let mut builder = konst::array::ArrayBuilder::new(); while !builder.is_full() { let index = builder.len() as u8; - let light = (index as f64 * 1.3) as u8; - let lighter = (index as f64 * 1.6) as u8; - let lightest = (index as f64 * 1.9) as u8; + let light = (index as f64 * 1.15) as u8; + let lighter = (index as f64 * 1.7) as u8; + let lightest = (index as f64 * 1.85) as u8; let dark = (index as f64 * 0.9) as u8; let darker = (index as f64 * 0.6) as u8; let darkest = (index as f64 * 0.3) as u8; From 9a12e0c7bab24cb708d503e860d93677ae306961 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 17 May 2025 21:56:16 +0300 Subject: [PATCH 077/178] output: format --- output/src/ops/transform.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 904eba4..8cb93ff 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -28,9 +28,15 @@ macro_rules! transform_xy { ($x:literal $y:literal $xy:literal |$self:ident : $Enum:ident, $to:ident|$area:expr) => { pub enum $Enum { X(T), Y(T), XY(T) } impl $Enum { - #[inline] pub const fn x (item: T) -> Self { Self::X(item) } - #[inline] pub const fn y (item: T) -> Self { Self::Y(item) } - #[inline] pub const fn xy (item: T) -> Self { Self::XY(item) } + #[inline] pub const fn x (item: T) -> Self { + Self::X(item) + } + #[inline] pub const fn y (item: T) -> Self { + Self::Y(item) + } + #[inline] pub const fn xy (item: T) -> Self { + Self::XY(item) + } } #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> TryFromDsl<'state, T> From 921378b6dbb38d4f301f688abd1cfef9bdc0f941 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 18 May 2025 00:22:48 +0300 Subject: [PATCH 078/178] tui: remove buttons --- tui/src/tui_content/tui_button.rs | 39 ------------------------------- 1 file changed, 39 deletions(-) diff --git a/tui/src/tui_content/tui_button.rs b/tui/src/tui_content/tui_button.rs index 2a297da..ec58935 100644 --- a/tui/src/tui_content/tui_button.rs +++ b/tui/src/tui_content/tui_button.rs @@ -1,40 +1 @@ use crate::{*, Color::*}; - -pub fn button_2 <'a> ( - key: impl Content + 'a, label: impl Content + 'a, editing: bool, -) -> impl Content + 'a { - let key = Tui::fg_bg(Tui::g(0), Tui::orange(), Bsp::e( - Tui::fg_bg(Tui::orange(), Reset, "▐"), - Bsp::e(key, Tui::fg(Tui::g(96), "▐")) - )); - let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)); - Tui::bold(true, Bsp::e(key, label)) -} - -pub fn button_3 <'a, K, L, V> ( - key: K, - label: L, - value: V, - editing: bool, -) -> impl Content + 'a where - K: Content + 'a, - L: Content + 'a, - V: Content + 'a, -{ - let key = Tui::fg_bg(Tui::g(0), Tui::orange(), - Bsp::e(Tui::fg_bg(Tui::orange(), Reset, "▐"), Bsp::e(key, Tui::fg(if editing { - Tui::g(128) - } else { - Tui::g(96) - }, "▐")))); - let label = Bsp::e( - When::new(!editing, Bsp::e( - Tui::fg_bg(Tui::g(255), Tui::g(96), label), - Tui::fg_bg(Tui::g(128), Tui::g(96), "▐"), - )), - Bsp::e( - Tui::fg_bg(Tui::g(224), Tui::g(128), value), - Tui::fg_bg(Tui::g(128), Reset, "▌"), - )); - Tui::bold(true, Bsp::e(key, label)) -} From 7ddbace030c23444ba26f0086f0aaf2985810c00 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 18 May 2025 18:31:40 +0300 Subject: [PATCH 079/178] proc: allow implementing #[command] over more complex types --- proc/src/lib.rs | 2 +- proc/src/proc_command.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 132d416..02ba5de 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -10,7 +10,7 @@ pub(crate) use proc_macro2::{ TokenStream as TokenStream2, Ident, Span, Punct, Group, Delimiter, Spacing::* }; pub(crate) use syn::{ - parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, + parse_macro_input, ImplItem, ImplItemFn, LitStr, Type, TypePath, ItemImpl, ReturnType, Signature, FnArg, Pat, PatType, PatIdent, parse::{Parse, ParseStream, Result}, }; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index f8e9d2a..123b11b 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -4,7 +4,7 @@ use crate::*; pub(crate) struct CommandDef(pub(crate) CommandMeta, pub(crate) CommandImpl); #[derive(Debug, Clone)] -pub(crate) struct CommandMeta(Ident); +pub(crate) struct CommandMeta(TypePath); #[derive(Debug, Clone)] pub(crate) struct CommandImpl(ItemImpl, BTreeMap, CommandArm>); @@ -14,7 +14,7 @@ struct CommandArm(Ident, Vec, #[allow(unused)] ReturnType); impl Parse for CommandMeta { fn parse (input: ParseStream) -> Result { - Ok(Self(input.parse::()?)) + Ok(Self(input.parse()?)) } } From 3bc739328eed0c8fa67b432c7354c7929ddb505f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 18 May 2025 18:32:07 +0300 Subject: [PATCH 080/178] output: somewhat better error handling in cond and either --- output/src/ops/cond.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 902a2dc..578210e 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -25,9 +25,9 @@ try_from_expr!(<'source, 'state, E>: When>: |state, iter| { let content = iter.next().expect("no content specified").value; return Some(Self( state.get(&mut iter) - .expect("no condition provided"), + .unwrap_or_else(||panic!("cond: no condition: {iter:?}")), state.get_content(&content) - .unwrap_or_else(||panic!("no content corresponding to for {:?}", &content)) + .unwrap_or_else(||panic!("cond: no content for {:?}: {iter:?}", &content)) )) } }); @@ -35,14 +35,16 @@ try_from_expr!(<'source, 'state, E>: When>: |state, iter| { #[cfg(feature = "dsl")] 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).expect("no condition provided"), + 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!("no content 1: {iter:?}")), + .unwrap_or_else(||panic!("either: no content 1: {base:?}")), state.get_content(&iter.next().expect("no alternate specified").value) - .unwrap_or_else(||panic!("no content 2: {iter:?}")), + .unwrap_or_else(||panic!("either: no content 2: {base:?}")), )) } }); From 90f5699fff48d2e8e0a24c36741a7d4ff771385d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 19 May 2025 00:06:03 +0300 Subject: [PATCH 081/178] dsl: use only Dsl trait --- dsl/src/dsl_provide.rs | 92 +++++++++++------- input/src/input_dsl.rs | 100 +++++++------------- output/src/ops/align.rs | 64 +++++++------ output/src/ops/bsp.rs | 56 ++++++----- output/src/ops/cond.rs | 56 ++++++----- output/src/ops/transform.rs | 85 ++++++++--------- output/src/view.rs | 112 +++++++++++----------- proc/src/proc_command.rs | 183 ++++++++++++++++++------------------ proc/src/proc_expose.rs | 14 +-- proc/src/proc_view.rs | 58 ++++++++---- tengri/src/lib.rs | 16 ++-- 11 files changed, 430 insertions(+), 406 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index c22e5d6..cf61968 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,55 +1,75 @@ use crate::*; -/// 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]. +/// 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 { - Ok(None) + fn take <'state, 'source> ( + &'state self, + _: &mut TokenIter<'source> + ) -> Perhaps { + unimplemented!() } fn take_or_fail <'state, 'source> ( - state: &'state Self, iter: &mut TokenIter<'source> + &'state self, + token: &mut TokenIter<'source>, + error: impl Into> ) -> Usually { - if let Some(value) = Self::take(state, iter)? { + if let Some(value) = Dsl::::take(self, token)? { Ok(value) } else { - Result::Err("not found".into()) // TODO add info and error type + Result::Err(error.into()) } } } -/// 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 - } +//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<'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> Dsl for T { +//} -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 FromDsl<'state, T>: Sized { + fn take_from <'source: 'state> ( + state: &'state T, + _token: &mut TokenIter<'source> + ) -> Perhaps { + unimplemented!() } -} - -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) + fn take_from_or_fail <'source: 'state> ( + state: &'state T, + token: &mut TokenIter<'source>, + error: impl Into> + ) -> Usually { + if let Some(value) = FromDsl::::take_from(state, token)? { + Ok(value) + } else { + Result::Err(error.into()) } - None } } - diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 4575e24..dfcbd72 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,25 +1,18 @@ 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<'state, S, C: DslCommand<'state, S>, I: DslInput> { +pub trait KeyMap, C: Command, I: DslInput> { /// Try to find a command that matches the current input event. - fn command (&'state self, state: &'state S, input: &'state I) -> Option; + fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -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 { +impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceIter<'source> { + fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { iter = rest; @@ -29,8 +22,8 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, &mut exp_iter) { - return Some(command) + if let Some(command) = Dsl::::take(state, &mut exp_iter)? { + return Ok(Some(command)) } } }, @@ -40,13 +33,13 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f _ => panic!("invalid config (expected expression)") } } - None + Ok(None) } } /// A [TokenIter] can be a [KeyMap]. -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 { +impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIter<'source> { + fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { match next { @@ -55,8 +48,8 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::try_from_expr(state, &mut e) { - return Some(command) + if let Some(command) = Dsl::::take(state, &mut e)? { + return Ok(Some(command)) } } }, @@ -66,44 +59,29 @@ impl<'state, S, C: DslCommand<'state, S>, I: DslInput> KeyMap<'state, S, C, I> f _ => panic!("invalid config (expected expression, got: {next:?})") } } - None + Ok(None) } } -pub type InputLayerCond<'state, S> = Boxbool + Send + Sync + 'state>; +pub type InputLayerCond = BoxUsually + Send + Sync>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. -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)>, +pub struct InputMap +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { + __: PhantomData<(S, C, I)>, + pub layers: Vec<(InputLayerCond, M)>, } -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 -{ +impl Default for InputMap +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { fn default () -> Self { - Self { - __: &PhantomData, - layers: vec![] - } + Self { __: PhantomData, layers: vec![] } } } -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 -{ +impl InputMap +where S: Dsl + 'static, C: Command, I: DslInput, M: KeyMap + Send + Sync { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -112,46 +90,38 @@ where self } pub fn add_layer (&mut self, keymap: M) -> &mut Self { - self.add_layer_if(Box::new(|_|true), keymap); + self.add_layer_if(Box::new(|_|Ok(true)), keymap); self } - pub fn layer_if (mut self, condition: InputLayerCond<'state, S>, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputLayerCond, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if (&mut self, condition: InputLayerCond<'state, S>, keymap: M) -> &mut Self { + pub fn add_layer_if (&mut self, condition: InputLayerCond, keymap: M) -> &mut Self { self.layers.push((Box::new(condition), keymap)); self } } -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 -{ +impl std::fmt::Debug for InputMap +where S: Dsl, C: Command, I: DslInput, M: KeyMap + 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<'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 { +impl KeyMap for InputMap +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { + fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { for (condition, keymap) in self.layers.iter() { - if !condition(state) { + if !condition(state)? { continue } - if let Some(command) = keymap.command(state, input) { - return Some(command) + if let Some(command) = keymap.keybind_resolve(state, input)? { + return Ok(Some(command)) } } - None + Ok(None) } } diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index d4c7fb3..a35659b 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,38 +36,42 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -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<'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) } } -}); +} 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 300d244..2819bbf 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,30 +21,40 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -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<'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 + }) } -}); +} 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 578210e..cfdf2fd 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -19,35 +19,41 @@ impl Either { } #[cfg(feature = "dsl")] -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)) - )) +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 + }) } -}); +} #[cfg(feature = "dsl")] -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<'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) } -}); +} 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 8cb93ff..80d50ce 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -39,30 +39,22 @@ macro_rules! transform_xy { } } #[cfg(feature = "dsl")] - 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(); + 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 { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { - 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!() - }) - } + 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!() + })) } - None + Ok(None) } } impl> Content for $Enum { @@ -92,31 +84,30 @@ 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>> 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<'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> Content for $Enum { diff --git a/output/src/view.rs b/output/src/view.rs index 484194d..85fac4b 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -8,15 +8,15 @@ use crate::*; 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 { + fn get_content_sym (&'a $self, iter: &Value<'a>) -> Perhaps> { + Ok(if let Value::Sym(s) = value { match *s { $($sym => Some($body.boxed()),)* _ => None } } else { - panic!("expected content, got: {value:?}") - } + return Err(format!("expected content, got: {iter:?}").into()) + }) } } } @@ -34,9 +34,11 @@ pub struct View<'a, T>( 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) { + while let Some(Token { value, .. }) = iter.peek() { + if let Ok(Some(content)) = self.0.get_content(&mut iter) { return Some(content) + // TODO handle errors here, how? + // error receiver trait in viewcontext? } } return None @@ -46,69 +48,61 @@ impl<'a, O: Output + 'a, T: ViewContext<'a, O>> Content for View<'a, T> { // Provides components to the view. #[cfg(feature = "dsl")] pub trait ViewContext<'state, E: Output + 'state>: Send + Sync - + Context<'state, bool> - + Context<'state, usize> - + Context<'state, E::Unit> + + Dsl + + Dsl + + Dsl { - 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_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_sym <'source: 'state> (&'state self, value: &Value<'source>) - -> Option>; - fn get_content_exp <'source: 'state> (&'state self, value: &Value<'source>) - -> Option> + fn get_content <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) + -> Perhaps> { - 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 + 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) } } #[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 - } + let value: Option<$T> = FromDsl::take_from($s, $dsl)?; + if let Some(value) = value { + return Ok(Some(value.boxed())) } } } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 123b11b..27f68ff 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -52,49 +52,111 @@ impl CommandImpl { impl ToTokens for CommandDef { fn to_tokens (&self, out: &mut TokenStream2) { - 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); + 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), + }) + }); + write_quote_to(out, quote! { /// Generated by [tengri_proc]. - #[derive(Clone, Debug)] - pub enum #enumeration { - #(#variants)* - } + #[derive(Clone, Debug)] pub enum #command_enum { #(#variants)* } + /// Not generated by [tengri_proc]. #block /// Generated by [tengri_proc]. - 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 - } + impl ::tengri::input::Command<#state> for #command_enum { + fn execute (self, state: &mut #state) -> Perhaps { + match self { #(#implementations)* } } } /// Generated by [tengri_proc]. - impl<'state> ::tengri::dsl::Context<'state, #enumeration> for #target { - fn get <'source> (&self, iter: &mut ::tengri::dsl::TokenIter<'source>) - -> Option<#enumeration> + impl ::tengri::dsl::Dsl<#command_enum> for #state { + fn take <'source, 'state> ( + &'state self, words: &mut TokenIter<'source> + ) + -> ::tengri::Perhaps<#command_enum> { - 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)* - } + let mut words = words.clone(); + let token = words.next(); + todo!()//Ok(match token { #(#matchers)* _ => None }) } } }); + //if exposed.len() > 0 { //panic!("{:#?}", block.self_ty); //if let Type::Path(ref path) = *block.self_ty { @@ -146,61 +208,4 @@ 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 4a11347..d6f23a8 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<'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) { + 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) { #predefined #(#values,)* - _ => return None - }) + _ => return Ok(None) + })) } } }); diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 7fdd1fd..f1fb4e2 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -52,7 +52,7 @@ impl ToTokens for ViewDef { }).collect(); let available: String = available.join(", "); let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{value:?}}, available: {available}"), + &format!("expected Sym(content), got: {{iter:?}}, available: {available}"), Span::call_site() ); for token in quote! { @@ -60,15 +60,16 @@ impl ToTokens for ViewDef { /// Generated by [tengri_proc]. impl ::tengri::output::Content<#output> for #ident { fn content (&self) -> impl Render<#output> { + // TODO move to self.view() self.size.of(::tengri::output::View(self, self.config.view)) } } /// Generated by [tengri_proc]. impl<'state> ::tengri::output::ViewContext<'state, #output> for #ident { - fn get_content_sym <'source: 'state> (&'state self, value: &Value<'source>) - -> Option> + fn get_content_sym <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) + -> ::tengri::Perhaps> { - match value { #(#exposed)* _ => panic!(#error_msg) } + Ok(match iter.peek() { #(#exposed)* _ => panic!(#error_msg) }) } } } { @@ -80,21 +81,44 @@ impl ToTokens for ViewDef { impl ToTokens for ViewArm { fn to_tokens (&self, out: &mut TokenStream2) { let Self(key, value) = self; - 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(Ident::new("Some", Span::call_site())); out.append(Group::new(Delimiter::Parenthesis, { let mut out = TokenStream2::new(); - out.append(LitStr::new(key, Span::call_site()).token()); + 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 })); out.append(Punct::new('=', Joint)); diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 25d7b27..d451e70 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::{TryFromDsl, TokenIter}; + use crate::dsl::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::{Context, Value}; + //use crate::dsl::{Dsl, Value}; //struct Test; //#[tengri_proc::expose] @@ -91,10 +91,10 @@ pub use ::tengri_core::*; //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)); + //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)); //} From f08593f0f8c3dc03a734d922d2442848a4205ad6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 19 May 2025 02:23:26 +0300 Subject: [PATCH 082/178] remove View; allow rendering Result --- dsl/src/dsl_provide.rs | 50 ++++++++++++++++----------------- output/src/space/measure.rs | 2 +- output/src/view.rs | 55 ++++--------------------------------- proc/src/proc_view.rs | 22 +++++++-------- tui/src/tui_content.rs | 24 ++++++++++++++++ 5 files changed, 64 insertions(+), 89 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index cf61968..f98130b 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -16,19 +16,35 @@ use crate::*; /// 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 { +pub trait Dsl: Sized { + fn take <'state, 'source> (&'state self, _: &mut TokenIter<'source>) -> Perhaps { unimplemented!() } fn take_or_fail <'state, 'source> ( &'state self, token: &mut TokenIter<'source>, error: impl Into> - ) -> Usually { - if let Some(value) = Dsl::::take(self, token)? { + ) -> Usually { + if let Some(value) = Dsl::::take(self, token)? { + Ok(value) + } else { + Result::Err(error.into()) + } + } +} + +pub trait FromDsl<'state, State>: Sized { + fn take_from <'source: 'state> (state: &'state State, _token: &mut TokenIter<'source>) + -> Perhaps + { + unimplemented!() + } + 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()) @@ -53,23 +69,3 @@ impl<'state, X, Y> Dsl for Y where Y: FromDsl<'state, X> { //impl> Dsl for T { //} - -pub trait FromDsl<'state, T>: Sized { - fn take_from <'source: 'state> ( - state: &'state T, - _token: &mut TokenIter<'source> - ) -> Perhaps { - unimplemented!() - } - fn take_from_or_fail <'source: 'state> ( - state: &'state T, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - if let Some(value) = FromDsl::::take_from(state, token)? { - Ok(value) - } else { - Result::Err(error.into()) - } - } -} diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs index a894b60..9ed5c2c 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 85fac4b..8f40cc2 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -1,47 +1,12 @@ use crate::*; -#[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, iter: &Value<'a>) -> Perhaps> { - Ok(if let Value::Sym(s) = value { - match *s { - $($sym => Some($body.boxed()),)* - _ => None - } - } else { - return Err(format!("expected content, got: {iter:?}").into()) - }) - } - } - } -} - -// 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.peek() { - if let Ok(Some(content)) = self.0.get_content(&mut iter) { - return Some(content) - // TODO handle errors here, how? - // error receiver trait in viewcontext? - } +#[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())) } - return None } } @@ -96,13 +61,3 @@ pub trait ViewContext<'state, E: Output + 'state>: Send + Sync Ok(None) } } - -#[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())) - } - } -} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index f1fb4e2..48af79f 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 ident = &block.self_ty; + let view = &block.self_ty; let mut available = vec![]; let exposed: Vec<_> = exposed.iter().map(|(k,v)|{ available.push(k.clone()); @@ -52,24 +52,24 @@ impl ToTokens for ViewDef { }).collect(); let available: String = available.join(", "); let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{iter:?}}, available: {available}"), + &format!("expected Sym(content), got: {{token:?}}, available: {available}"), Span::call_site() ); for token in quote! { #block /// Generated by [tengri_proc]. - impl ::tengri::output::Content<#output> for #ident { + impl ::tengri::output::Content<#output> for #view { fn content (&self) -> impl Render<#output> { - // TODO move to self.view() - self.size.of(::tengri::output::View(self, self.config.view)) + self.view() } } /// Generated by [tengri_proc]. - impl<'state> ::tengri::output::ViewContext<'state, #output> for #ident { - fn get_content_sym <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) - -> ::tengri::Perhaps> - { - Ok(match iter.peek() { #(#exposed)* _ => panic!(#error_msg) }) + 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 }) } } } { @@ -126,7 +126,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("self", Span::call_site())); + out.append(Ident::new("state", Span::call_site())); out.append(Punct::new('.', Alone)); out.append(value.clone()); out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 38960b9..0d563fd 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -21,6 +21,30 @@ 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::*; From f797a7143dcee3b7d4b41ed32c80804dbe403936 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 20 May 2025 16:27:05 +0300 Subject: [PATCH 083/178] extact dsl_token; flip Dsl; try to obviate ViewContext --- dsl/src/dsl_parse.rs | 109 ----------------------------------- dsl/src/dsl_provide.rs | 106 +++++++++++++++++++--------------- dsl/src/dsl_token.rs | 110 ++++++++++++++++++++++++++++++++++++ dsl/src/lib.rs | 1 + input/src/input_dsl.rs | 22 ++++---- output/src/ops/align.rs | 2 +- output/src/ops/bsp.rs | 2 +- output/src/ops/cond.rs | 4 +- output/src/ops/stack.rs | 71 +++++++++++++---------- output/src/ops/transform.rs | 4 +- output/src/view.rs | 10 ++-- proc/src/proc_view.rs | 32 ++++++++++- 12 files changed, 264 insertions(+), 209 deletions(-) create mode 100644 dsl/src/dsl_token.rs diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs index 81a0336..63bc567 100644 --- a/dsl/src/dsl_parse.rs +++ b/dsl/src/dsl_parse.rs @@ -171,112 +171,3 @@ pub const fn to_digit (c: char) -> DslResult { _ => return Result::Err(Unexpected(c)) }) } - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { - pub source: &'source str, - pub start: usize, - pub length: usize, - pub value: Value<'source>, -} - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { - #[default] Nil, - Err(DslError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, TokenIter<'source>), -} - -impl<'source> std::fmt::Display for Value<'source> { - fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) - } -} - -impl<'source> Token<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: Value<'source> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: Value<'source>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> Value { - self.value - } - pub const fn error (self, error: DslError) -> Self { - Self { value: Value::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) { - Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let Exp(depth, _) = token.value { - token.with_value(Exp(depth, TokenIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - token.with_value(Value::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - if depth > 0 { - token.with_value(Value::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index f98130b..4a4e3bd 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,12 +1,49 @@ use crate::*; +pub trait Dsl: Sized { + fn take_from <'state, 'source: 'state> (state: &'state State, _: &mut TokenIter<'source>) + -> Perhaps + { + unimplemented!() + } + fn take_from_or_fail <'state, 'source: 'state> ( + state: &'state State, + token: &mut TokenIter<'source>, + error: impl Into> + ) -> Usually { + if let Some(value) = Dsl::::take_from(state, token)? { + Ok(value) + } else { + Result::Err(error.into()) + } + } +} + +impl> DslFrom for T {} + +pub trait DslFrom>: Sized { + fn take <'state, 'source: 'state> (&'state self, token: &mut TokenIter<'source>) + -> Perhaps + { + T::take_from(self, token) + } + fn take_or_fail <'state, 'source: 'state> ( + &'state self, + token: &mut TokenIter<'source>, + error: impl Into> + ) -> Usually { + T::take_from_or_fail(self, token, error) + } +} + /// 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>, + impl ::tengri::dsl::Dsl<$S> for $T { + fn take_from <'state, 'source: 'state> ( + state: &'state $S, + $iter: &mut ::tengri::dsl::TokenIter<'source>, ) -> ::tengri::Perhaps<$T> { $expr } @@ -14,43 +51,24 @@ use crate::*; } } -/// 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!() - } - fn take_or_fail <'state, 'source> ( - &'state self, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - if let Some(value) = Dsl::::take(self, token)? { - Ok(value) - } else { - Result::Err(error.into()) - } - } -} - -pub trait FromDsl<'state, State>: Sized { - fn take_from <'source: 'state> (state: &'state State, _token: &mut TokenIter<'source>) - -> Perhaps - { - unimplemented!() - } - 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()) - } - } -} +///// 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!() + //} + //fn take_or_fail <'state, 'source> ( + //&'state self, + //token: &mut TokenIter<'source>, + //error: impl Into> + //) -> Usually { + //if let Some(value) = Dsl::::take(self, token)? { + //Ok(value) + //} else { + //Result::Err(error.into()) + //} + //} +//} //impl, U> Dsl for &T { //fn take <'state, 'source> (&'state self, iter: &mut TokenIter<'source>) -> Perhaps { @@ -64,8 +82,8 @@ pub trait FromDsl<'state, State>: Sized { //} //} -impl<'state, X, Y> Dsl for Y where Y: FromDsl<'state, X> { -} - -//impl> Dsl for T { +//impl<'state, X, Y> Dsl for Y where Y: Dsl<'state, X> { +//} + +//impl> Dsl for T { //} diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs new file mode 100644 index 0000000..d45203b --- /dev/null +++ b/dsl/src/dsl_token.rs @@ -0,0 +1,110 @@ +use crate::*; + +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { + pub source: &'source str, + pub start: usize, + pub length: usize, + pub value: Value<'source>, +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { + #[default] Nil, + Err(DslError), + Num(usize), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, TokenIter<'source>), +} + +impl<'source> std::fmt::Display for Value<'source> { + fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(_, e) => format!("{e:?}"), + }) + } +} + +impl<'source> Token<'source> { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: Value<'source> + ) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: Value<'source>) -> Self { + Self { value, ..self } + } + pub const fn value (&self) -> Value { + self.value + } + pub const fn error (self, error: DslError) -> Self { + Self { value: Value::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) { + Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, + Result::Err(e) => Self { value: Err(e), ..self.grow() }, + } + } + pub const fn grow_key (self) -> Self { + let token = self.grow(); + token.with_value(Key(token.slice_source(self.source))) + } + pub const fn grow_sym (self) -> Self { + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.source))) + } + pub const fn grow_str (self) -> Self { + let token = self.grow(); + token.with_value(Str(token.slice_source(self.source))) + } + pub const fn grow_exp (self) -> Self { + let token = self.grow(); + if let Exp(depth, _) = token.value { + token.with_value(Exp(depth, TokenIter::new(token.slice_source_exp(self.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + token.with_value(Value::Exp(depth.saturating_add(1), source)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + if depth > 0 { + token.with_value(Value::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 9c9b44e..712eced 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -46,6 +46,7 @@ pub(crate) use self::Value::*; pub(crate) use self::DslError::*; mod dsl_error; pub use self::dsl_error::*; +mod dsl_token; pub use self::dsl_token::*; mod dsl_parse; pub use self::dsl_parse::*; mod dsl_provide; pub use self::dsl_provide::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index dfcbd72..67b51ae 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,13 @@ use std::marker::PhantomData; 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 + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceIter<'source> { +impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for SourceIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { @@ -22,7 +22,7 @@ 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)? { + if let Some(command) = Dsl::take_from(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -38,7 +38,7 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceI } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIter<'source> { +impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for TokenIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { @@ -48,7 +48,7 @@ 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)? { + if let Some(command) = C::take_from(state, &mut e)? { return Ok(Some(command)) } } @@ -68,20 +68,20 @@ pub type InputLayerCond = BoxUsually + Send + Sync>; /// 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 { +where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { __: PhantomData<(S, C, I)>, pub layers: Vec<(InputLayerCond, M)>, } impl Default for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { fn default () -> Self { Self { __: PhantomData, layers: vec![] } } } -impl InputMap -where S: Dsl + 'static, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl InputMap +where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -104,7 +104,7 @@ where S: Dsl + 'static, C: Command, I: DslInput, M: KeyMap + Send } impl std::fmt::Debug for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "[InputMap: {} layer(s)]", self.layers.len()) } @@ -112,7 +112,7 @@ where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { /// An [InputMap] can be a [KeyMap]. impl KeyMap for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { for (condition, keymap) in self.layers.iter() { if !condition(state)? { diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index a35659b..7a9377f 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -37,7 +37,7 @@ pub struct Align(Alignment, A); #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for Align> { +Dsl 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 { diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 2819bbf..895916c 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -22,7 +22,7 @@ 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>> { +Dsl 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"), diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index cfdf2fd..8d8a27e 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -20,7 +20,7 @@ impl Either { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for When> { +Dsl for When> { fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("when"), @@ -39,7 +39,7 @@ FromDsl<'state, T> for When> { #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -FromDsl<'state, T> for Either, RenderBox<'state, E>> { +Dsl 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(); diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs index 816713c..6498ab0 100644 --- a/output/src/ops/stack.rs +++ b/output/src/ops/stack.rs @@ -1,4 +1,5 @@ use crate::*; +use Direction::*; pub struct Stack { __: PhantomData, @@ -10,68 +11,76 @@ impl Stack { Self { direction, callback, __: Default::default(), } } pub fn north (callback: F) -> Self { - Self::new(Direction::North, callback) + Self::new(North, callback) } pub fn south (callback: F) -> Self { - Self::new(Direction::South, callback) + Self::new(South, callback) } pub fn east (callback: F) -> Self { - Self::new(Direction::East, callback) + Self::new(East, callback) } pub fn west (callback: F) -> Self { - Self::new(Direction::West, callback) + Self::new(West, callback) } } impl)->())->()> Content for Stack { fn layout (&self, mut to: E::Area) -> E::Area { let mut x = to.x(); let mut y = to.y(); - let mut w = to.w(); - let mut h = to.h(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); (self.callback)(&mut move |component: &dyn Render|{ - let layout = component.layout([x, y, w, h].into()); + let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); match self.direction { - Direction::North => { - todo!() + South => { + y = y.plus(h); + h_used = h_used.plus(h); + h_remaining = h_remaining.minus(h); + w_used = w_used.max(w); }, - Direction::South => { - y = y + layout.h(); - h = h.minus(layout.h()); + East => { + x = x.plus(w); + w_used = w_used.plus(w); + w_remaining = w_remaining.minus(w); + h_used = h_used.max(h); }, - Direction::East => { - x = x + layout.w(); - w = w.minus(layout.w()); - }, - Direction::West => { + North | West => { todo!() }, _ => unreachable!(), } }); - to + match self.direction { + North | West => { + todo!() + }, + South | East => { + [to.x(), to.y(), w_used.into(), h_used.into()].into() + }, + _ => unreachable!(), + } } fn render (&self, to: &mut E) { let mut x = to.x(); let mut y = to.y(); - let mut w = to.w(); - let mut h = to.h(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); (self.callback)(&mut move |component: &dyn Render|{ - let layout = component.layout([x, y, w, h].into()); + let layout = component.layout([x, y, w_remaining, h_remaining].into()); match self.direction { - Direction::North => { - todo!() - }, - Direction::South => { - y = y + layout.h(); - h = h.minus(layout.h()); + South => { + y = y.plus(layout.h()); + h_remaining = h_remaining.minus(layout.h()); + h_used = h_used.plus(layout.h()); to.place(layout, component); }, - Direction::East => { - x = x + layout.w(); - w = w.minus(layout.w()); + East => { + x = x.plus(layout.w()); + w_remaining = w_remaining.minus(layout.w()); + w_used = w_used.plus(layout.h()); to.place(layout, component); }, - Direction::West => { + North | West => { todo!() }, _ => unreachable!() diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 80d50ce..b94def2 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -40,7 +40,7 @@ macro_rules! transform_xy { } #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - FromDsl<'state, T> for $Enum> { + Dsl for $Enum> { fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { let mut base = iter.clone(); @@ -85,7 +85,7 @@ macro_rules! transform_xy_unit { } #[cfg(feature = "dsl")] impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - FromDsl<'state, T> for $Enum> { + Dsl 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(); diff --git a/output/src/view.rs b/output/src/view.rs index 8f40cc2..460f1c5 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -3,7 +3,7 @@ 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)?; + let value: Option<$T> = Dsl::take_from($s, $dsl)?; if let Some(value) = value { return Ok(Some(value.boxed())) } @@ -12,10 +12,10 @@ use crate::*; // Provides components to the view. #[cfg(feature = "dsl")] -pub trait ViewContext<'state, E: Output + 'state>: Send + Sync - + Dsl - + Dsl - + Dsl +pub trait ViewContext<'state, E: Output + 'state>: Send + Sync where + bool: Dsl, + usize: Dsl, + E::Unit: Dsl, { fn get_content_or_fail <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) -> Usually> diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 48af79f..6947766 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -64,12 +64,39 @@ impl ToTokens for ViewDef { } } /// Generated by [tengri_proc]. - impl<'state> ::tengri::dsl::FromDsl<'state, #view> for ::tengri::output::RenderBox<'state, #output> { + 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 }) + Ok(match token.peek() { + Some(::tengri::dsl::Token { + ::tengri::dsl::value: Value::Exp(exp), .. + }) => { + let value: Option<$T> = FromDsl::take_from($s, $dsl)?; + if let Some(value) = value { + return Ok(Some(value.boxed())) + } + 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>>); + None + }, + #(#exposed),* + _ => None + }) } } } { @@ -135,6 +162,5 @@ impl ToTokens for ViewArm { out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); out })); - out.append(Punct::new(',', Alone)); } } From 7c1cddc759390552119e732dcda6ac56a71326f2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 20 May 2025 18:48:55 +0300 Subject: [PATCH 084/178] wip: directionalize! can't fit all into 1 trait because of directionality of trait implementation rules and constraints :( --- dsl/src/dsl_provide.rs | 66 ++++++++++++--------- input/src/input_dsl.rs | 20 +++---- output/src/ops/align.rs | 13 ++--- output/src/ops/bsp.rs | 29 +++++----- output/src/ops/cond.rs | 55 +++++++++--------- output/src/ops/transform.rs | 56 ++++++++---------- output/src/view.rs | 111 +++++++++++++++++------------------- proc/src/proc_command.rs | 11 ++-- proc/src/proc_expose.rs | 13 +++-- proc/src/proc_view.rs | 69 ++++++++++++---------- 10 files changed, 221 insertions(+), 222 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 4a4e3bd..92aae3d 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,49 +1,61 @@ use crate::*; -pub trait Dsl: Sized { - fn take_from <'state, 'source: 'state> (state: &'state State, _: &mut TokenIter<'source>) - -> Perhaps - { - unimplemented!() - } - fn take_from_or_fail <'state, 'source: 'state> ( - state: &'state State, +pub trait Dsl: Sized { + fn take <'state, 'source: 'state> ( + &'state self, + token: &mut TokenIter<'source> + ) + -> Perhaps; + fn take_or_fail <'state, 'source: 'state> ( + &'state self, token: &mut TokenIter<'source>, error: impl Into> - ) -> Usually { - if let Some(value) = Dsl::::take_from(state, token)? { + ) -> Usually { + if let Some(value) = Dsl::::take(self, token)? { Ok(value) } else { Result::Err(error.into()) } } } - -impl> DslFrom for T {} - -pub trait DslFrom>: Sized { +pub trait FromDsl: Sized { + fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) + -> Perhaps; + fn take_from_or_fail <'state, '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> Dsl for State { fn take <'state, 'source: 'state> (&'state self, token: &mut TokenIter<'source>) -> Perhaps { - T::take_from(self, token) - } - fn take_or_fail <'state, 'source: 'state> ( - &'state self, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - T::take_from_or_fail(self, token, error) + FromDsl::take_from(self, token) } } +//impl, T> FromDsl for T { + //fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) + //-> Perhaps + //{ + //Dsl::take(state, token) + //} +//} /// 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<$S> for $T { +#[macro_export] macro_rules! from_dsl { + ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { + impl ::tengri::dsl::FromDsl<$S> for $T { fn take_from <'state, 'source: 'state> ( - state: &'state $S, - $iter: &mut ::tengri::dsl::TokenIter<'source>, + $state: &'state $S, + $words: &mut ::tengri::dsl::TokenIter<'source>, ) -> ::tengri::Perhaps<$T> { $expr } diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 67b51ae..3c4e8a6 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,13 @@ use std::marker::PhantomData; pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. -pub trait KeyMap + Command, I: DslInput> { +pub trait KeyMap, C: Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for SourceIter<'source> { +impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { @@ -22,7 +22,7 @@ impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for Source match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Dsl::take_from(state, &mut exp_iter)? { + if let Some(command) = state.take(&mut exp_iter)? { return Ok(Some(command)) } } @@ -38,7 +38,7 @@ impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for Source } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for TokenIter<'source> { +impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { @@ -48,7 +48,7 @@ impl<'source, S, C: Dsl + Command, I: DslInput> KeyMap for TokenI match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = C::take_from(state, &mut e)? { + if let Some(command) = state.take(&mut e)? { return Ok(Some(command)) } } @@ -68,20 +68,20 @@ pub type InputLayerCond = BoxUsually + Send + Sync>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. pub struct InputMap -where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { __: PhantomData<(S, C, I)>, pub layers: Vec<(InputLayerCond, M)>, } impl Default for InputMap -where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { fn default () -> Self { Self { __: PhantomData, layers: vec![] } } } impl InputMap -where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -104,7 +104,7 @@ where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { } impl std::fmt::Debug for InputMap -where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "[InputMap: {} layer(s)]", self.layers.len()) } @@ -112,7 +112,7 @@ where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { /// An [InputMap] can be a [KeyMap]. impl KeyMap for InputMap -where C: Dsl + Command, I: DslInput, M: KeyMap + Send + Sync { +where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { for (condition, keymap) in self.layers.iter() { if !condition(state)? { diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 7a9377f..9d902ad 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,20 +36,17 @@ 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>> -Dsl for Align> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { +impl, A> FromDsl for Align { + fn take_from <'state, 'source: 'state> (state: &'state State, 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); - }; + let content = state.take_or_fail(&mut iter.clone(), "expected content")?; return Ok(Some(match key { "align/c" => Self::c(content), "align/x" => Self::x(content), diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 895916c..7e961dd 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,9 +21,8 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -Dsl for Bsp, RenderBox<'state, E>> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { +impl + Dsl> FromDsl for Bsp { + fn take_from <'state, '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"), .. @@ -31,23 +30,23 @@ Dsl for Bsp, RenderBox<'state, E>> { 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)?), + Self::n(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), Some(Token { value: Value::Key("bsp/s"), .. }) => - Self::s(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), + Self::s(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), Some(Token { value: Value::Key("bsp/e"), .. }) => - Self::e(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), + Self::e(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), Some(Token { value: Value::Key("bsp/w"), .. }) => - Self::w(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), + Self::w(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), Some(Token { value: Value::Key("bsp/a"), .. }) => - Self::a(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), + Self::a(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), Some(Token { value: Value::Key("bsp/b"), .. }) => - Self::b(state.get_content_or_fail(iter)?, - state.get_content_or_fail(iter)?), + Self::b(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), _ => unreachable!(), })) } else { diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 8d8a27e..ca5ea61 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -17,44 +17,26 @@ impl Either { Self(c, a, b) } } - #[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -Dsl for When> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { +impl + Dsl> FromDsl for When { + fn take_from <'state, 'source: 'state> ( + state: &'state T, + token: &mut TokenIter<'source> + ) -> Perhaps { Ok(if let Some(Token { value: Value::Key("when"), .. - }) = iter.peek() { - let base = iter.clone(); + }) = token.peek() { + let base = token.clone(); return Ok(Some(Self( - state.take(iter)?.unwrap_or_else(||panic!("cond: no condition: {base:?}")), - state.get_content_or_fail(iter)? + state.take_or_fail(token, "cond: no condition")?, + state.take_or_fail(token, "cond: no content")?, ))) } else { None }) } } - -#[cfg(feature = "dsl")] -impl<'state, E: Output + 'state, T: ViewContext<'state, E>> -Dsl 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) - } -} - impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, item) = self; @@ -73,7 +55,24 @@ impl> Content for When { if *cond { item.render(to) } } } - +#[cfg(feature = "dsl")] +impl + Dsl + Dsl> FromDsl for Either { + fn take_from <'state, 'source: 'state> ( + state: &'state T, + token: &mut TokenIter<'source> + ) -> Perhaps { + if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { + let base = token.clone(); + let _ = token.next().unwrap(); + return Ok(Some(Self( + state.take_or_fail(token, "either: no condition")?, + state.take_or_fail(token, "either: no content 1")?, + state.take_or_fail(token, "either: no content 2")?, + ))) + } + Ok(None) + } +} impl, B: Render> Content for Either { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, a, b) = self; diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index b94def2..c589bfd 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -26,31 +26,24 @@ use crate::*; /// 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(T), Y(T), XY(T) } - impl $Enum { - #[inline] pub const fn x (item: T) -> Self { - Self::X(item) - } - #[inline] pub const fn y (item: T) -> Self { - Self::Y(item) - } - #[inline] pub const fn xy (item: T) -> Self { - Self::XY(item) - } + 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")] - impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - Dsl for $Enum> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { + impl> FromDsl for $Enum { + fn take_from <'state, 'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { 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)?), + Self::x(state.take_or_fail(iter, "x: no content")?), Some(Token{value:Value::Key($y),..}) => - Self::y(state.get_content_or_fail(iter)?), + Self::y(state.take_or_fail(iter, "y: no content")?), Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.get_content_or_fail(iter)?), + Self::xy(state.take_or_fail(iter, "xy: no content")?), _ => unreachable!() })) } @@ -77,31 +70,30 @@ macro_rules! transform_xy { /// 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, T), Y(U, T), XY(U, U, T), } - impl $Enum { - #[inline] pub const fn x (x: U, item: T) -> Self { Self::X(x, item) } - #[inline] pub const fn y (y: U, item: T) -> Self { Self::Y(y, item) } - #[inline] pub const fn xy (x: U, y: U, item: T) -> Self { Self::XY(x, y, item) } + 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")] - impl<'state, E: Output + 'state, T: ViewContext<'state, E>> - Dsl for $Enum> { - fn take_from <'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { + impl + Dsl> FromDsl for $Enum { + fn take_from <'state, '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)?, + state.take_or_fail(iter, "x: no unit")?, + state.take_or_fail(iter, "x: no content")?, ), Some(Token { value: Value::Key($y), .. }) => Self::y( - state.take_or_fail(iter, "no unit specified")?, - state.get_content_or_fail(iter)?, + state.take_or_fail(iter, "y: no unit")?, + state.take_or_fail(iter, "y: no content")?, ), 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)? + state.take_or_fail(iter, "xy: no unit x")?, + state.take_or_fail(iter, "xy: no unit y")?, + state.take_or_fail(iter, "xy: no content")? ), _ => unreachable!(), }) diff --git a/output/src/view.rs b/output/src/view.rs index 460f1c5..2ae266e 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -1,63 +1,54 @@ use crate::*; -#[cfg(feature = "dsl")] -#[macro_export] macro_rules! try_delegate { - ($s:ident, $dsl:expr, $T:ty) => { - let value: Option<$T> = Dsl::take_from($s, $dsl)?; - if let Some(value) = value { - return Ok(Some(value.boxed())) - } - } -} +//#[cfg(feature = "dsl")] +//#[macro_export] macro_rules! try_delegate { + //($s:ident, $dsl:expr, $T:ty) => { + //let value: Option<$T> = Dsl::take_from($s, $dsl)?; + //if let Some(value) = value { + //return Ok(Some(value.boxed())) + //} + //} +//} -// Provides components to the view. -#[cfg(feature = "dsl")] -pub trait ViewContext<'state, E: Output + 'state>: Send + Sync where - bool: Dsl, - usize: Dsl, - E::Unit: Dsl, -{ - 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, iter: &mut TokenIter<'source>) - -> Perhaps> - { - 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) - } -} +//// Provides components to the view. +//#[cfg(feature = "dsl")] +//pub trait ViewContext<'state, E: Output + 'state>: + //FromDsl + FromDsl + FromDsl + Send + Sync +//{ + //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) + //} +//} + +//#[cfg(feature = "dsl")] +//impl<'context, O: Output + 'context, T: ViewContext<'context, O>> FromDsl for RenderBox<'context, O> { + //fn take_from <'state, 'source: 'state> (state: &'state T, token: &mut TokenIter<'source>) + //-> Perhaps> + //{ + //Ok(if let Some(content) = state.get_content_sym(token)? { + //Some(content) + //} else if let Some(content) = state.get_content_exp(token)? { + //Some(content) + //} else { + //None + //}) + //} +//} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 27f68ff..9d83f9f 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -144,12 +144,11 @@ impl ToTokens for CommandDef { } } /// 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 ::tengri::dsl::FromDsl<#state> for #command_enum { + fn take_from <'state, 'source: 'state> ( + state: &'state #state, + words: &mut TokenIter<'source>, + ) -> Perhaps { let mut words = words.clone(); let token = words.next(); todo!()//Ok(match token { #(#matchers)* _ => None }) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index d6f23a8..887121d 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -60,7 +60,7 @@ impl ToTokens for ExposeDef { impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self(block, exposed) = self; - let target = &block.self_ty; + let exposed_impl_type = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { let formatted_type = format!("{}", quote! { #t }); @@ -84,12 +84,13 @@ 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> + /// Generated by [tengriproc]. + impl ::tengri::dsl::Dsl<#t> for #exposed_impl_type { + fn take <'state, 'source: 'state> ( + &'state self, + words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps<#t> { - Ok(Some(match iter.next().map(|x|x.value) { + Ok(Some(match words.next().map(|x|x.value) { #predefined #(#values,)* _ => return Ok(None) diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 6947766..321fb26 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -41,15 +41,33 @@ impl Parse for ViewImpl { } } +fn builtins () -> [TokenStream2;14] { + [ + quote! { When:: }, + quote! { Either:: }, + quote! { Align:: }, + quote! { Bsp:: }, + quote! { Fill:: }, + quote! { Fixed::<_, A> }, + quote! { Min::<_, A> }, + quote! { Max::<_, A> }, + quote! { Shrink::<_, A> }, + quote! { Expand::<_, A> }, + quote! { Push::<_, A> }, + quote! { Pull::<_, A> }, + quote! { Margin::<_, A> }, + quote! { Padding::<_, A> }, + ] +} + 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 mut available = vec![]; - let exposed: Vec<_> = exposed.iter().map(|(k,v)|{ - available.push(k.clone()); - ViewArm(k.clone(), v.clone()) - }).collect(); + let exposed: Vec<_> = exposed.iter() + .map(|(k,v)|{ available.push(k.clone()); ViewArm(k.clone(), v.clone()) }) + .collect(); let available: String = available.join(", "); let error_msg = LitStr::new( &format!("expected Sym(content), got: {{token:?}}, available: {available}"), @@ -58,43 +76,33 @@ impl ToTokens for ViewDef { for token in quote! { #block /// Generated by [tengri_proc]. + /// + /// Delegates the rendering of [#view] to the [#view::view} method, + /// which you will need to implement, e.g. passing a [TokenIter] + /// containing a layout and keybindings config from user dirs. impl ::tengri::output::Content<#output> for #view { fn content (&self) -> impl Render<#output> { self.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> + /// + /// Gives [#view] the ability to construct the [Render]able + /// which might corresponds to a given [TokenStream], + /// while taking [#view]'s state into consideration. + impl<'context> ::tengri::dsl::FromDsl<#view> for RenderBox<'context, #output> { + fn take_from <'state, 'source: 'state> ( + state: &'state #view, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { - Ok(match token.peek() { - Some(::tengri::dsl::Token { - ::tengri::dsl::value: Value::Exp(exp), .. - }) => { - let value: Option<$T> = FromDsl::take_from($s, $dsl)?; - if let Some(value) = value { + Ok(match words.peek() { + Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Exp(exp), .. }) => { + if let Some(value) = FromDsl::take_from(state, words)? { return Ok(Some(value.boxed())) } - 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>>); + //#builtins None }, - #(#exposed),* + #(#exposed)* _ => None }) } @@ -162,5 +170,6 @@ impl ToTokens for ViewArm { out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); out })); + out.append(Punct::new(',', Alone)); } } From 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 20 May 2025 22:02:51 +0300 Subject: [PATCH 085/178] read explicit lifetime to FromDsl --- dsl/src/dsl_provide.rs | 47 +++++---- input/src/input_dsl.rs | 64 ++++++++---- input/src/input_handle.rs | 4 +- output/src/ops/align.rs | 60 +++++------ output/src/ops/bsp.rs | 62 ++++++------ output/src/ops/cond.rs | 8 +- output/src/ops/thunk.rs | 47 ++++----- output/src/ops/transform.rs | 12 +-- output/src/output.rs | 26 ++--- proc/src/proc_command.rs | 17 ++-- proc/src/proc_expose.rs | 16 +-- proc/src/proc_view.rs | 170 ++++++++++---------------------- tui/src/tui_engine.rs | 3 +- tui/src/tui_engine/tui_input.rs | 2 +- 14 files changed, 252 insertions(+), 286 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 92aae3d..d9896de 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -18,10 +18,10 @@ pub trait Dsl: Sized { } } } -pub trait FromDsl: Sized { - fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) +pub trait FromDsl<'source, State>: Sized { + fn take_from <'state> (state: &'state State, token: &mut TokenIter<'source>) -> Perhaps; - fn take_from_or_fail <'state, 'source: 'state> ( + fn take_from_or_fail <'state> ( state: &'state State, token: &mut TokenIter<'source>, error: impl Into> @@ -33,13 +33,6 @@ pub trait FromDsl: Sized { } } } -impl, State> Dsl for State { - fn take <'state, 'source: 'state> (&'state self, token: &mut TokenIter<'source>) - -> Perhaps - { - FromDsl::take_from(self, token) - } -} //impl, T> FromDsl for T { //fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) //-> Perhaps @@ -51,16 +44,36 @@ impl, State> Dsl for State { /// Implement the [Dsl] trait, which boils down to /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { - ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl ::tengri::dsl::FromDsl<$S> for $T { - fn take_from <'state, 'source: 'state> ( - $state: &'state $S, - $words: &mut ::tengri::dsl::TokenIter<'source>, - ) -> ::tengri::Perhaps<$T> { + (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { + impl<'source, State: Dsl, A> FromDsl<'source, State> for $T { + fn take_from <'state> ( + $state: &'state State, + $words: &mut TokenIter<'source>, + ) -> Perhaps<$T> { $expr } } - } + }; + (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { + impl<'source, State: Dsl + Dsl, A, B> FromDsl<'source, State> for $T { + fn take_from <'state> ( + $state: &'state State, + $words: &mut TokenIter<'source>, + ) -> Perhaps<$T> { + $expr + } + } + }; + ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { + impl<'source> FromDsl<'source, $S> for $T { + fn take_from <'state> ( + $state: &'state $S, + $words: &mut TokenIter<'source>, + ) -> Perhaps<$T> { + $expr + } + } + }; } ///// Maps a sequencer of EDN tokens to parameters of supported types diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 3c4e8a6..2856c89 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,15 @@ use std::marker::PhantomData; 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<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceIter<'source> { +impl<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> +KeyMap<'source, S, C, I> +for SourceIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { @@ -22,7 +24,7 @@ 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) = state.take(&mut exp_iter)? { + if let Some(command) = FromDsl::take_from(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -38,7 +40,9 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for SourceI } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIter<'source> { +impl<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> +KeyMap<'source, S, C, I> +for TokenIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { @@ -48,7 +52,7 @@ 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) = state.take(&mut e)? { + if let Some(command) = FromDsl::take_from(state, &mut e)? { return Ok(Some(command)) } } @@ -63,25 +67,37 @@ impl<'source, S: Dsl, C: Command, I: DslInput> KeyMap for TokenIt } } -pub type InputLayerCond = BoxUsually + Send + Sync>; +pub type InputLayerCond<'source, S> = BoxUsually + 'source>; /// 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<'source, + S, + C: Command + FromDsl<'source, S>, + I: DslInput, + M: KeyMap<'source, S, C, I> +> { + __: PhantomData<&'source (S, C, I)>, + pub layers: Vec<(InputLayerCond<'source, S>, M)>, } -impl Default for InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl<'source, + S, + C: Command + FromDsl<'source, S>, + I: DslInput, + M: KeyMap<'source, S, C, I> +> Default for InputMap<'source, S, C, I, M>{ fn default () -> Self { Self { __: PhantomData, layers: vec![] } } } -impl InputMap -where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { +impl<'source, + S, + C: Command + FromDsl<'source, S>, + I: DslInput, + M: KeyMap<'source, S, C, I> +> InputMap<'source, S, C, I, M> { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -93,26 +109,34 @@ where S: Dsl, C: Command, I: DslInput, M: KeyMap + Send + Sync { self.add_layer_if(Box::new(|_|Ok(true)), keymap); self } - pub fn layer_if (mut self, condition: InputLayerCond, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputLayerCond<'source, 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<'source, 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<'source, + S, + C: Command + FromDsl<'source, S>, + I: DslInput, + M: KeyMap<'source, S, C, I> +> std::fmt::Debug for InputMap<'source, S, C, I, M> { 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 { +impl<'source, + S, + C: Command + FromDsl<'source, S>, + I: DslInput, + M: KeyMap<'source, S, C, I> +> KeyMap<'source, S, C, I> for InputMap<'source, S, C, I, M> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { for (condition, keymap) in self.layers.iter() { if !condition(state)? { diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs index 99bce29..2be276c 100644 --- a/input/src/input_handle.rs +++ b/input/src/input_handle.rs @@ -2,7 +2,7 @@ use crate::*; use std::sync::{Mutex, Arc, RwLock}; /// Event source -pub trait Input: Send + Sync + Sized { +pub trait Input: Sized { /// Type of input event type Event; /// Result of handling input @@ -36,7 +36,7 @@ pub trait Input: Send + Sync + Sized { } /// Handle input -pub trait Handle: Send + Sync { +pub trait Handle { fn handle (&mut self, _input: &E) -> Perhaps { Ok(None) } diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 9d902ad..5702a40 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,39 +36,33 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -impl, A> FromDsl for Align { - fn take_from <'state, 'source: 'state> (state: &'state State, 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 = state.take_or_fail(&mut iter.clone(), "expected content")?; - 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) - } +from_dsl!(@a: 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 = state.take_or_fail(&mut iter.clone(), "expected content")?; + 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) +}); impl Align { #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } @@ -85,7 +79,7 @@ impl Align { } impl> Content for Align { - fn content (&self) -> impl Render { + fn content (&self) -> impl Render + '_ { &self.1 } fn layout (&self, on: E::Area) -> E::Area { diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 7e961dd..6062485 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,39 +21,35 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -impl + Dsl> FromDsl for Bsp { - fn take_from <'state, '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.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/s"), .. }) => - Self::s(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/e"), .. }) => - Self::e(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/w"), .. }) => - Self::w(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/a"), .. }) => - Self::a(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/b"), .. }) => - Self::b(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - _ => unreachable!(), - })) - } else { - None - }) - } -} +from_dsl!(@ab: Bsp: |state, iter|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.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + Some(Token { value: Value::Key("bsp/s"), .. }) => + Self::s(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + Some(Token { value: Value::Key("bsp/e"), .. }) => + Self::e(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + Some(Token { value: Value::Key("bsp/w"), .. }) => + Self::w(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + Some(Token { value: Value::Key("bsp/a"), .. }) => + Self::a(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + Some(Token { value: Value::Key("bsp/b"), .. }) => + Self::b(state.take_or_fail(iter, "expected content 1")?, + state.take_or_fail(iter, "expected content 2")?), + _ => unreachable!(), + })) +} else { + 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 ca5ea61..e0dde77 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -18,8 +18,8 @@ impl Either { } } #[cfg(feature = "dsl")] -impl + Dsl> FromDsl for When { - fn take_from <'state, 'source: 'state> ( +impl<'source, A, T: Dsl + Dsl> FromDsl<'source, T> for When { + fn take_from <'state> ( state: &'state T, token: &mut TokenIter<'source> ) -> Perhaps { @@ -56,8 +56,8 @@ impl> Content for When { } } #[cfg(feature = "dsl")] -impl + Dsl + Dsl> FromDsl for Either { - fn take_from <'state, 'source: 'state> ( +impl<'source, A, B, T: Dsl + Dsl + Dsl> FromDsl<'source, T> for Either { + fn take_from <'state> ( state: &'state T, token: &mut TokenIter<'source> ) -> Perhaps { diff --git a/output/src/ops/thunk.rs b/output/src/ops/thunk.rs index 9d2c0bf..d0e2877 100644 --- a/output/src/ops/thunk.rs +++ b/output/src/ops/thunk.rs @@ -2,72 +2,67 @@ use crate::*; use std::marker::PhantomData; /// Lazily-evaluated [Render]able. -pub struct Thunk, F: Fn()->T + Send + Sync>( +pub struct Thunk, F: Fn()->T>( PhantomData, F ); -impl, F: Fn()->T + Send + Sync> Thunk { +impl, F: Fn()->T> Thunk { pub const fn new (thunk: F) -> Self { Self(PhantomData, thunk) } } -impl, F: Fn()->T + Send + Sync> Content for Thunk { +impl, F: Fn()->T> Content for Thunk { fn content (&self) -> impl Render { (self.1)() } } -pub struct ThunkBox<'a, E: Output>( +pub struct ThunkBox( PhantomData, - BoxRenderBox<'a, E> + Send + Sync + 'a> + BoxBox>>, ); -impl<'a, E: Output> ThunkBox<'a, E> { - pub const fn new (thunk: BoxRenderBox<'a, E> + Send + Sync + 'a>) -> Self { +impl ThunkBox { + pub const fn new (thunk: BoxBox>>) -> Self { Self(PhantomData, thunk) } } -impl<'a, E: Output> Content for ThunkBox<'a, E> { - fn content (&self) -> impl Render { (self.1)() } +impl Content for ThunkBox { + fn content (&self) -> impl Render { (&self.1)() } } -impl<'a, E, F, T> From for ThunkBox<'a, E> -where - E: Output, - F: Fn()->T + Send + Sync + 'a, - T: Render + Send + Sync + 'a -{ - fn from (f: F) -> Self { - Self(PhantomData, Box::new(move||f().boxed())) +impl FromBox>>> for ThunkBox { + fn from (f: BoxBox>>) -> Self { + Self(PhantomData, f) } } -//impl<'a, E: Output, F: Fn()->Box + 'a> + Send + Sync + 'a> From for ThunkBox<'a, E> { +//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { //fn from (f: F) -> Self { //Self(Default::default(), Box::new(f)) //} //} -pub struct ThunkRender(PhantomData, F); -impl ThunkRender { +pub struct ThunkRender(PhantomData, F); +impl ThunkRender { pub fn new (render: F) -> Self { Self(PhantomData, render) } } -impl Content for ThunkRender { +impl Content for ThunkRender { fn render (&self, to: &mut E) { (self.1)(to) } } pub struct ThunkLayout< E: Output, - F1: Fn(E::Area)->E::Area + Send + Sync, - F2: Fn(&mut E) + Send + Sync + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) >( PhantomData, F1, F2 ); -implE::Area + Send + Sync, F2: Fn(&mut E) + Send + Sync> ThunkLayout { +implE::Area, F2: Fn(&mut E)> ThunkLayout { pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } } impl Content for ThunkLayout where E: Output, - F1: Fn(E::Area)->E::Area + Send + Sync, - F2: Fn(&mut E) + Send + Sync + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) { fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } fn render (&self, to: &mut E) { (self.2)(to) } diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index c589bfd..808f56b 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,8 +33,8 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl> FromDsl for $Enum { - fn take_from <'state, 'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { + impl<'source, A, T: Dsl> FromDsl<'source, T> for $Enum { + fn take_from <'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { let mut base = iter.clone(); return Ok(Some(match iter.next() { @@ -51,7 +51,7 @@ macro_rules! transform_xy { } } impl> Content for $Enum { - fn content (&self) -> impl Render { + fn content (&self) -> impl Render + '_ { match self { Self::X(item) => item, Self::Y(item) => item, @@ -77,8 +77,8 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl + Dsl> FromDsl for $Enum { - fn take_from <'state, 'source: 'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { + impl<'source, A, U: Coordinate, T: Dsl + Dsl> FromDsl<'source, T> for $Enum { + fn take_from <'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() { @@ -103,7 +103,7 @@ macro_rules! transform_xy_unit { } } impl> Content for $Enum { - fn content (&self) -> impl Render { + fn content (&self) -> impl Render + '_ { Some(match self { Self::X(_, content) => content, Self::Y(_, content) => content, diff --git a/output/src/output.rs b/output/src/output.rs index b5cc1fc..07c8ac2 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -29,8 +29,8 @@ pub trait Render { /// Write data to display. fn render (&self, output: &mut E); /// Perform type erasure, turning `self` into an opaque [RenderBox]. - fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Send + Sync + Sized + 'a { - Box::new(self) as RenderBox<'a, E> + fn boxed <'a> (self) -> Box + 'a> where Self: Sized + 'a { + Box::new(self) as Box + 'a> } } @@ -47,20 +47,20 @@ impl> Render for C { /// Opaque pointer to a renderable living on the heap. /// /// Return this from [Content::content] to use dynamic dispatch. -pub type RenderBox<'a, E> = Box>; +pub type RenderBox = Box>; /// You can render from a box. -impl<'a, E: Output> Content for RenderBox<'a, E> { - fn content (&self) -> impl Render { self.deref() } +impl Content for RenderBox { + fn content (&self) -> impl Render + '_ { self.deref() } //fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self } } /// Opaque pointer to a renderable. -pub type RenderDyn<'a, E> = dyn Render + Send + Sync + 'a; +pub type RenderDyn = dyn Render; /// You can render from an opaque pointer. -impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { - fn content (&self) -> impl Render { +impl Content for &RenderDyn where Self: Sized { + fn content (&self) -> impl Render + '_ { #[allow(suspicious_double_ref_op)] self.deref() } @@ -77,7 +77,7 @@ impl<'a, E: Output> Content for &RenderDyn<'a, E> where Self: Sized { /// Composable renderable with static dispatch. pub trait Content { /// Return a [Render]able of a specific type. - fn content (&self) -> impl Render { () } + fn content (&self) -> impl Render + '_ { () } /// Perform layout. By default, delegates to [Self::content]. fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) } /// Draw to output. By default, delegates to [Self::content]. @@ -86,7 +86,7 @@ pub trait Content { /// Every pointer to [Content] is a [Content]. impl> Content for &C { - fn content (&self) -> impl Render { (*self).content() } + fn content (&self) -> impl Render + '_ { (*self).content() } fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) } fn render (&self, output: &mut E) { (*self).render(output) } } @@ -98,7 +98,7 @@ impl Content for () { } impl> Content for Option { - fn content (&self) -> impl Render { + fn content (&self) -> impl Render + '_ { self.as_ref() } fn layout (&self, area: E::Area) -> E::Area { @@ -117,7 +117,7 @@ impl> Content for Option { // Implement for all [Output]s. (|$self:ident:$Struct:ty| $content:expr) => { impl Content for $Struct { - fn content (&$self) -> impl Render { Some($content) } + fn content (&$self) -> impl Render + '_ { Some($content) } } }; // Implement for specific [Output]. @@ -127,7 +127,7 @@ impl> Content for Option { |$content:expr) => { impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output> for $Struct $(<$($($L)? $($T)?),+>)? { - fn content (&$self) -> impl Render<$Output> { $content } + fn content (&$self) -> impl Render<$Output> + '_ { $content } } }; } diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 9d83f9f..a8ca8c9 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::take_or_fail(self, words)?, + #arg: FromDsl::take_from_or_fail(self, words)?, }); } out @@ -137,17 +137,22 @@ impl ToTokens for CommandDef { #[derive(Clone, Debug)] pub enum #command_enum { #(#variants)* } /// Not generated by [tengri_proc]. #block - /// Generated by [tengri_proc]. + /// Generated by [tengri_proc::command]. + /// + /// Means [#command_enum] is now a [Command] over [#state]. + /// Instances of [#command_enum] can be consumed by a + /// mutable pointer to [#state], invoking predefined operations + /// and optionally returning undo history data. impl ::tengri::input::Command<#state> for #command_enum { fn execute (self, state: &mut #state) -> Perhaps { match self { #(#implementations)* } } } - /// Generated by [tengri_proc]. - impl ::tengri::dsl::FromDsl<#state> for #command_enum { - fn take_from <'state, 'source: 'state> ( + /// Generated by [tengri_proc::command]. + impl<'source> ::tengri::dsl::FromDsl<'source, #state> for #command_enum { + fn take_from <'state> ( state: &'state #state, - words: &mut TokenIter<'source>, + words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { let mut words = words.clone(); let token = words.next(); diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 887121d..f7b934e 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -60,7 +60,7 @@ impl ToTokens for ExposeDef { impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self(block, exposed) = self; - let exposed_impl_type = &block.self_ty; + let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { let formatted_type = format!("{}", quote! { #t }); @@ -84,15 +84,15 @@ impl ToTokens for ExposeImpl { }; let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { - /// Generated by [tengriproc]. - impl ::tengri::dsl::Dsl<#t> for #exposed_impl_type { - fn take <'state, 'source: 'state> ( - &'state self, + /// Generated by [tengri_proc::expose]. + impl<'source> ::tengri::dsl::FromDsl<'source, #state> for #t { + fn take_from <'state> ( + state: &'state #state, words: &mut ::tengri::dsl::TokenIter<'source> - ) -> Perhaps<#t> { + ) -> Perhaps { Ok(Some(match words.next().map(|x|x.value) { #predefined - #(#values,)* + #(#values)* _ => return Ok(None) })) } @@ -116,7 +116,7 @@ impl ToTokens for ExposeArm { let Self(key, value) = self; let key = LitStr::new(&key, Span::call_site()); write_quote_to(out, quote! { - Some(::tengri::dsl::Value::Sym(#key)) => self.#value() + Some(::tengri::dsl::Value::Sym(#key)) => state.#value(), }) } } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 321fb26..70511eb 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -14,8 +14,6 @@ pub(crate) struct ViewImpl { exposed: BTreeMap, } -struct ViewArm(String, Ident); - impl Parse for ViewMeta { fn parse (input: ParseStream) -> Result { Ok(Self { @@ -41,6 +39,60 @@ 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 mut available = vec![]; + let exposed: Vec<_> = exposed.iter().map(|(key, value)|{ + available.push(key.clone()); + write_quote(quote! { #key => Some(state.#value().boxed()), }) + }).collect(); + let available: String = available.join(", "); + let error_msg = LitStr::new( + &format!("expected Sym(content), got: {{token:?}}, available: {available}"), + Span::call_site() + ); + write_quote_to(out, quote! { + #block + /// Generated by [tengri_proc]. + /// + /// Delegates the rendering of [#view] to the [#view::view} method, + /// which you will need to implement, e.g. passing a [TokenIter] + /// containing a layout and keybindings config from user dirs. + impl ::tengri::output::Content<#output> for #view { + fn content (&self) -> impl Render<#output> { + self.view() + } + } + /// Generated by [tengri_proc]. + /// + /// Gives [#view] the ability to construct the [Render]able + /// which might corresponds to a given [TokenStream], + /// while taking [#view]'s state into consideration. + impl<'source> ::tengri::dsl::FromDsl<'source, #view> for Box + 'source> { + fn take_from <'state> ( + state: &'state #view, + words: &mut ::tengri::dsl::TokenIter<'source>, + ) -> Perhaps { + Ok(words.peek().and_then(|::tengri::dsl::Token{ value, .. }|match value { + ::tengri::dsl::Value::Exp(_, exp) => { + todo!("builtin layout ops"); + //#builtins + None + }, + ::tengri::dsl::Value::Sym(sym) => match sym { + #(#exposed)* + _ => None + }, + _ => None + })) + } + } + }) + } +} + fn builtins () -> [TokenStream2;14] { [ quote! { When:: }, @@ -59,117 +111,3 @@ fn builtins () -> [TokenStream2;14] { quote! { Padding::<_, A> }, ] } - -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 mut available = vec![]; - let exposed: Vec<_> = exposed.iter() - .map(|(k,v)|{ available.push(k.clone()); ViewArm(k.clone(), v.clone()) }) - .collect(); - let available: String = available.join(", "); - let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{token:?}}, available: {available}"), - Span::call_site() - ); - for token in quote! { - #block - /// Generated by [tengri_proc]. - /// - /// Delegates the rendering of [#view] to the [#view::view} method, - /// which you will need to implement, e.g. passing a [TokenIter] - /// containing a layout and keybindings config from user dirs. - impl ::tengri::output::Content<#output> for #view { - fn content (&self) -> impl Render<#output> { - self.view() - } - } - /// Generated by [tengri_proc]. - /// - /// Gives [#view] the ability to construct the [Render]able - /// which might corresponds to a given [TokenStream], - /// while taking [#view]'s state into consideration. - impl<'context> ::tengri::dsl::FromDsl<#view> for RenderBox<'context, #output> { - fn take_from <'state, 'source: 'state> ( - state: &'state #view, words: &mut ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { - Ok(match words.peek() { - Some(::tengri::dsl::Token { value: ::tengri::dsl::Value::Exp(exp), .. }) => { - if let Some(value) = FromDsl::take_from(state, words)? { - return Ok(Some(value.boxed())) - } - //#builtins - None - }, - #(#exposed)* - _ => None - }) - } - } - } { - out.append(token) - } - } -} - -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(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 - })); - out.append(Punct::new('=', Joint)); - out.append(Punct::new('>', Alone)); - 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(Punct::new('.', Alone)); - out.append(value.clone()); - out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); - out.append(Punct::new('.', Alone)); - out.append(Ident::new("boxed", Span::call_site())); - out.append(Group::new(Delimiter::Parenthesis, TokenStream2::new())); - out - })); - out.append(Punct::new(',', Alone)); - } -} diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 0969cc3..b8d60f4 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -72,7 +72,8 @@ pub trait TuiRun + Handle + 'static> { fn run (&self, state: &Arc>) -> Usually<()>; } -impl + Handle + 'static> TuiRun for Arc> { +impl + Handle + Send + Sync + 'static> +TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 75c5be2..c9cd788 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -21,7 +21,7 @@ impl Input for TuiIn { impl TuiIn { /// Spawn the input thread. - pub fn run_input + 'static> ( + pub fn run_input + Send + Sync + 'static> ( engine: &Arc>, state: &Arc>, timer: Duration From f714302f21d64ef5f6dcbee09aa913e8e3b9fbc5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 00:06:36 +0300 Subject: [PATCH 086/178] FromDsl -> Namespace --- dsl/src/dsl_provide.rs | 14 +++++++------- input/src/input_dsl.rs | 29 +++++++++++++++-------------- output/src/ops/cond.rs | 6 +++--- output/src/ops/transform.rs | 4 ++-- output/src/view.rs | 4 ++-- proc/src/proc_command.rs | 4 ++-- proc/src/proc_expose.rs | 2 +- proc/src/proc_view.rs | 2 +- 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index d9896de..cfb6b93 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,6 +1,6 @@ use crate::*; -pub trait Dsl: Sized { +pub trait Dsl { fn take <'state, 'source: 'state> ( &'state self, token: &mut TokenIter<'source> @@ -18,7 +18,7 @@ pub trait Dsl: Sized { } } } -pub trait FromDsl<'source, State>: Sized { +pub trait Namespace<'source, State>: Sized { fn take_from <'state> (state: &'state State, token: &mut TokenIter<'source>) -> Perhaps; fn take_from_or_fail <'state> ( @@ -26,14 +26,14 @@ pub trait FromDsl<'source, State>: Sized { token: &mut TokenIter<'source>, error: impl Into> ) -> Usually { - if let Some(value) = FromDsl::::take_from(state, token)? { + if let Some(value) = Namespace::::take_from(state, token)? { Ok(value) } else { Result::Err(error.into()) } } } -//impl, T> FromDsl for T { +//impl, T> Namespace for T { //fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) //-> Perhaps //{ @@ -45,7 +45,7 @@ pub trait FromDsl<'source, State>: Sized { /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'source, State: Dsl, A> FromDsl<'source, State> for $T { + impl<'source, State: Dsl, A> Namespace<'source, State> for $T { fn take_from <'state> ( $state: &'state State, $words: &mut TokenIter<'source>, @@ -55,7 +55,7 @@ pub trait FromDsl<'source, State>: Sized { } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'source, State: Dsl + Dsl, A, B> FromDsl<'source, State> for $T { + impl<'source, State: Dsl + Dsl, A, B> Namespace<'source, State> for $T { fn take_from <'state> ( $state: &'state State, $words: &mut TokenIter<'source>, @@ -65,7 +65,7 @@ pub trait FromDsl<'source, State>: Sized { } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl<'source> FromDsl<'source, $S> for $T { + impl<'source> Namespace<'source, $S> for $T { fn take_from <'state> ( $state: &'state $S, $words: &mut TokenIter<'source>, diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 2856c89..29d0031 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,13 @@ use std::marker::PhantomData; pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. -pub trait KeyMap<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> { +pub trait KeyMap<'source, S, C: Namespace<'source, S> + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> +impl<'source, S, C: Namespace<'source, S> + Command, I: DslInput> KeyMap<'source, S, C, I> for SourceIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { @@ -24,7 +24,7 @@ for SourceIter<'source> { match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = FromDsl::take_from(state, &mut exp_iter)? { + if let Some(command) = Namespace::take_from(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -40,7 +40,7 @@ for SourceIter<'source> { } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S, C: FromDsl<'source, S> + Command, I: DslInput> +impl<'source, S, C: Namespace<'source, S> + Command, I: DslInput> KeyMap<'source, S, C, I> for TokenIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { @@ -52,7 +52,7 @@ for TokenIter<'source> { match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = FromDsl::take_from(state, &mut e)? { + if let Some(command) = Namespace::take_from(state, &mut e)? { return Ok(Some(command)) } } @@ -67,23 +67,24 @@ for TokenIter<'source> { } } -pub type InputLayerCond<'source, S> = BoxUsually + 'source>; +pub type InputCondition<'source, S> = + BoxUsually + Send + Sync + 'source>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. pub struct InputMap<'source, S, - C: Command + FromDsl<'source, S>, + C: Command + Namespace<'source, S>, I: DslInput, M: KeyMap<'source, S, C, I> > { __: PhantomData<&'source (S, C, I)>, - pub layers: Vec<(InputLayerCond<'source, S>, M)>, + pub layers: Vec<(InputCondition<'source, S>, M)>, } impl<'source, S, - C: Command + FromDsl<'source, S>, + C: Command + Namespace<'source, S>, I: DslInput, M: KeyMap<'source, S, C, I> > Default for InputMap<'source, S, C, I, M>{ @@ -94,7 +95,7 @@ impl<'source, impl<'source, S, - C: Command + FromDsl<'source, S>, + C: Command + Namespace<'source, S>, I: DslInput, M: KeyMap<'source, S, C, I> > InputMap<'source, S, C, I, M> { @@ -109,11 +110,11 @@ impl<'source, self.add_layer_if(Box::new(|_|Ok(true)), keymap); self } - pub fn layer_if (mut self, condition: InputLayerCond<'source, S>, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputCondition<'source, S>, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if (&mut self, condition: InputLayerCond<'source, S>, keymap: M) -> &mut Self { + pub fn add_layer_if (&mut self, condition: InputCondition<'source, S>, keymap: M) -> &mut Self { self.layers.push((Box::new(condition), keymap)); self } @@ -121,7 +122,7 @@ impl<'source, impl<'source, S, - C: Command + FromDsl<'source, S>, + C: Command + Namespace<'source, S>, I: DslInput, M: KeyMap<'source, S, C, I> > std::fmt::Debug for InputMap<'source, S, C, I, M> { @@ -133,7 +134,7 @@ impl<'source, /// An [InputMap] can be a [KeyMap]. impl<'source, S, - C: Command + FromDsl<'source, S>, + C: Command + Namespace<'source, S>, I: DslInput, M: KeyMap<'source, S, C, I> > KeyMap<'source, S, C, I> for InputMap<'source, S, C, I, M> { diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index e0dde77..67e5e16 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -14,11 +14,11 @@ pub struct Either(pub bool, pub A, pub B); impl Either { /// Create a ternary condition. pub const fn new (c: bool, a: A, b: B) -> Self { - Self(c, a, b) + Self(c, a, b) } } #[cfg(feature = "dsl")] -impl<'source, A, T: Dsl + Dsl> FromDsl<'source, T> for When { +impl<'source, A, T: Dsl + Dsl> Namespace<'source, T> for When { fn take_from <'state> ( state: &'state T, token: &mut TokenIter<'source> @@ -56,7 +56,7 @@ impl> Content for When { } } #[cfg(feature = "dsl")] -impl<'source, A, B, T: Dsl + Dsl + Dsl> FromDsl<'source, T> for Either { +impl<'source, A, B, T: Dsl + Dsl + Dsl> Namespace<'source, T> for Either { fn take_from <'state> ( state: &'state T, token: &mut TokenIter<'source> diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 808f56b..e5a86c4 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,7 +33,7 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl<'source, A, T: Dsl> FromDsl<'source, T> for $Enum { + impl<'source, A, T: Dsl> Namespace<'source, T> for $Enum { fn take_from <'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { let mut base = iter.clone(); @@ -77,7 +77,7 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl<'source, A, U: Coordinate, T: Dsl + Dsl> FromDsl<'source, T> for $Enum { + impl<'source, A, U: Coordinate, T: Dsl + Dsl> Namespace<'source, T> for $Enum { fn take_from <'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(); diff --git a/output/src/view.rs b/output/src/view.rs index 2ae266e..ae252d9 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -13,7 +13,7 @@ use crate::*; //// Provides components to the view. //#[cfg(feature = "dsl")] //pub trait ViewContext<'state, E: Output + 'state>: - //FromDsl + FromDsl + FromDsl + Send + Sync + //Namespace + Namespace + Namespace + Send + Sync //{ //fn get_content_sym <'source: 'state> (&'state self, iter: &mut TokenIter<'source>) //-> Perhaps>; @@ -39,7 +39,7 @@ use crate::*; //} //#[cfg(feature = "dsl")] -//impl<'context, O: Output + 'context, T: ViewContext<'context, O>> FromDsl for RenderBox<'context, O> { +//impl<'context, O: Output + 'context, T: ViewContext<'context, O>> Namespace for RenderBox<'context, O> { //fn take_from <'state, 'source: 'state> (state: &'state T, token: &mut TokenIter<'source>) //-> Perhaps> //{ diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index a8ca8c9..e518462 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: FromDsl::take_from_or_fail(self, words)?, + #arg: Namespace::take_from_or_fail(self, words)?, }); } out @@ -149,7 +149,7 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'source> ::tengri::dsl::FromDsl<'source, #state> for #command_enum { + impl<'source> ::tengri::dsl::Namespace<'source, #state> for #command_enum { fn take_from <'state> ( state: &'state #state, words: &mut ::tengri::dsl::TokenIter<'source> diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index f7b934e..f3940e1 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,7 +85,7 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl<'source> ::tengri::dsl::FromDsl<'source, #state> for #t { + impl<'source> ::tengri::dsl::Namespace<'source, #state> for #t { fn take_from <'state> ( state: &'state #state, words: &mut ::tengri::dsl::TokenIter<'source> diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 70511eb..4e6eeb2 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -70,7 +70,7 @@ impl ToTokens for ViewDef { /// Gives [#view] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#view]'s state into consideration. - impl<'source> ::tengri::dsl::FromDsl<'source, #view> for Box + 'source> { + impl<'source> ::tengri::dsl::Namespace<'source, #view> for Box + 'source> { fn take_from <'state> ( state: &'state #view, words: &mut ::tengri::dsl::TokenIter<'source>, From 2048dd2263fc4389c8f510bfd12c851efe34026f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 01:44:09 +0300 Subject: [PATCH 087/178] output: add Memo --- output/src/ops.rs | 1 + output/src/ops/memo.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 output/src/ops/memo.rs diff --git a/output/src/ops.rs b/output/src/ops.rs index 4c6ea2a..7eba655 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -2,6 +2,7 @@ mod align; pub use self::align::*; mod bsp; pub use self::bsp::*; mod cond; pub use self::cond::*; mod map; pub use self::map::*; +mod memo; pub use self::memo::*; mod stack; pub use self::stack::*; //mod reduce; pub use self::reduce::*; mod thunk; pub use self::thunk::*; diff --git a/output/src/ops/memo.rs b/output/src/ops/memo.rs new file mode 100644 index 0000000..07e2d93 --- /dev/null +++ b/output/src/ops/memo.rs @@ -0,0 +1,30 @@ +use crate::*; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Default)] pub struct Memo { + pub value: T, + pub view: Arc> +} + +impl Memo { + pub fn new (value: T, view: U) -> Self { + Self { value, view: Arc::new(view.into()) } + } + pub fn update ( + &mut self, + newval: T, + render: impl Fn(&mut U, &T, &T)->R + ) -> Option { + if newval != self.value { + let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); + self.value = newval; + return Some(result); + } + None + } +} + +/// Clear a pre-allocated buffer, then write into it. +#[macro_export] macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } +} From 776cea6f1ba502b87bcaed3a7d794076e328df2e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 02:50:21 +0300 Subject: [PATCH 088/178] dsl: reduce number of lifetime arguments --- dsl/src/dsl_provide.rs | 54 ++++++++++++------------ input/src/input_dsl.rs | 16 ++++---- output/src/ops/cond.rs | 12 +++--- output/src/ops/transform.rs | 44 +++++++++++--------- proc/src/proc_command.rs | 6 +-- proc/src/proc_expose.rs | 6 +-- proc/src/proc_view.rs | 68 +++++++++++++++++-------------- tui/src/tui_content.rs | 23 ++++++----- tui/src/tui_content/tui_number.rs | 5 +++ 9 files changed, 126 insertions(+), 108 deletions(-) create mode 100644 tui/src/tui_content/tui_number.rs diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index cfb6b93..cbd99ff 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,30 +1,29 @@ use crate::*; -pub trait Dsl { - fn take <'state, 'source: 'state> ( - &'state self, - token: &mut TokenIter<'source> - ) - -> Perhaps; - fn take_or_fail <'state, 'source: 'state> ( - &'state self, - token: &mut TokenIter<'source>, - error: impl Into> - ) -> Usually { - if let Some(value) = Dsl::::take(self, token)? { +pub trait Dsl { + fn take <'source> (&self, token: &mut TokenIter<'source>) -> Perhaps; + fn take_or_fail <'source> ( + &self, token: &mut TokenIter<'source>, error: impl Into> + ) -> Usually { + if let Some(value) = Dsl::::take(self, token)? { Ok(value) } else { Result::Err(error.into()) } } } -pub trait Namespace<'source, State>: Sized { - fn take_from <'state> (state: &'state State, token: &mut TokenIter<'source>) + +impl, State> Dsl for State { + fn take <'source> (&self, token: &mut TokenIter<'source>) -> Perhaps { + Namespace::take_from(self, token) + } +} + +pub trait Namespace: Sized { + fn take_from <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps; - fn take_from_or_fail <'state> ( - state: &'state State, - token: &mut TokenIter<'source>, - error: impl Into> + fn take_from_or_fail <'source> ( + state: &State, token: &mut TokenIter<'source>, error: impl Into> ) -> Usually { if let Some(value) = Namespace::::take_from(state, token)? { Ok(value) @@ -33,6 +32,7 @@ pub trait Namespace<'source, State>: Sized { } } } + //impl, T> Namespace for T { //fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) //-> Perhaps @@ -45,9 +45,9 @@ pub trait Namespace<'source, State>: Sized { /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'source, State: Dsl, A> Namespace<'source, State> for $T { - fn take_from <'state> ( - $state: &'state State, + impl, A> Namespace for $T { + fn take_from <'source> ( + $state: &State, $words: &mut TokenIter<'source>, ) -> Perhaps<$T> { $expr @@ -55,9 +55,9 @@ pub trait Namespace<'source, State>: Sized { } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'source, State: Dsl + Dsl, A, B> Namespace<'source, State> for $T { - fn take_from <'state> ( - $state: &'state State, + impl + Dsl, A, B> Namespace for $T { + fn take_from <'source> ( + $state: &State, $words: &mut TokenIter<'source>, ) -> Perhaps<$T> { $expr @@ -65,9 +65,9 @@ pub trait Namespace<'source, State>: Sized { } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl<'source> Namespace<'source, $S> for $T { - fn take_from <'state> ( - $state: &'state $S, + impl Namespace<$S> for $T { + fn take_from <'source> ( + $state: &$S, $words: &mut TokenIter<'source>, ) -> Perhaps<$T> { $expr diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 29d0031..046417a 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,13 @@ use std::marker::PhantomData; pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. -pub trait KeyMap<'source, S, C: Namespace<'source, S> + Command, I: DslInput> { +pub trait KeyMap<'source, S, C: Namespace + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S, C: Namespace<'source, S> + Command, I: DslInput> +impl<'source, S, C: Namespace + Command, I: DslInput> KeyMap<'source, S, C, I> for SourceIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { @@ -40,7 +40,7 @@ for SourceIter<'source> { } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S, C: Namespace<'source, S> + Command, I: DslInput> +impl<'source, S, C: Namespace + Command, I: DslInput> KeyMap<'source, S, C, I> for TokenIter<'source> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { @@ -74,7 +74,7 @@ pub type InputCondition<'source, S> = /// which may be made available subject to given conditions. pub struct InputMap<'source, S, - C: Command + Namespace<'source, S>, + C: Command + Namespace, I: DslInput, M: KeyMap<'source, S, C, I> > { @@ -84,7 +84,7 @@ pub struct InputMap<'source, impl<'source, S, - C: Command + Namespace<'source, S>, + C: Command + Namespace, I: DslInput, M: KeyMap<'source, S, C, I> > Default for InputMap<'source, S, C, I, M>{ @@ -95,7 +95,7 @@ impl<'source, impl<'source, S, - C: Command + Namespace<'source, S>, + C: Command + Namespace, I: DslInput, M: KeyMap<'source, S, C, I> > InputMap<'source, S, C, I, M> { @@ -122,7 +122,7 @@ impl<'source, impl<'source, S, - C: Command + Namespace<'source, S>, + C: Command + Namespace, I: DslInput, M: KeyMap<'source, S, C, I> > std::fmt::Debug for InputMap<'source, S, C, I, M> { @@ -134,7 +134,7 @@ impl<'source, /// An [InputMap] can be a [KeyMap]. impl<'source, S, - C: Command + Namespace<'source, S>, + C: Command + Namespace, I: DslInput, M: KeyMap<'source, S, C, I> > KeyMap<'source, S, C, I> for InputMap<'source, S, C, I, M> { diff --git a/output/src/ops/cond.rs b/output/src/ops/cond.rs index 67e5e16..be950fe 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/cond.rs @@ -18,9 +18,9 @@ impl Either { } } #[cfg(feature = "dsl")] -impl<'source, A, T: Dsl + Dsl> Namespace<'source, T> for When { - fn take_from <'state> ( - state: &'state T, +impl + Dsl> Namespace for When { + fn take_from <'source> ( + state: &T, token: &mut TokenIter<'source> ) -> Perhaps { Ok(if let Some(Token { @@ -56,9 +56,9 @@ impl> Content for When { } } #[cfg(feature = "dsl")] -impl<'source, A, B, T: Dsl + Dsl + Dsl> Namespace<'source, T> for Either { - fn take_from <'state> ( - state: &'state T, +impl + Dsl + Dsl> Namespace for Either { + fn take_from <'source> ( + state: &T, token: &mut TokenIter<'source> ) -> Perhaps { if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index e5a86c4..776d95c 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,17 +33,19 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl<'source, A, T: Dsl> Namespace<'source, T> for $Enum { - fn take_from <'state> (state: &'state T, iter: &mut TokenIter<'source>) -> Perhaps { - if let Some(Token { value: Value::Key(k), .. }) = iter.peek() { - let mut base = iter.clone(); - return Ok(Some(match iter.next() { + impl> Namespace for $Enum { + fn take_from <'source> ( + state: &T, token: &mut TokenIter<'source> + ) -> Perhaps { + if let Some(Token { value: Value::Key(k), .. }) = token.peek() { + let mut base = token.clone(); + return Ok(Some(match token.next() { Some(Token{value:Value::Key($x),..}) => - Self::x(state.take_or_fail(iter, "x: no content")?), + Self::x(state.take_or_fail(token, "x: no content")?), Some(Token{value:Value::Key($y),..}) => - Self::y(state.take_or_fail(iter, "y: no content")?), + Self::y(state.take_or_fail(token, "y: no content")?), Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.take_or_fail(iter, "xy: no content")?), + Self::xy(state.take_or_fail(token, "xy: no content")?), _ => unreachable!() })) } @@ -77,23 +79,25 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl<'source, A, U: Coordinate, T: Dsl + Dsl> Namespace<'source, T> for $Enum { - fn take_from <'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() { + impl + Dsl> Namespace for $Enum { + fn take_from <'source> ( + state: &T, token: &mut TokenIter<'source> + ) -> Perhaps { + Ok(if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = token.peek() { + let mut base = token.clone(); + Some(match token.next() { Some(Token { value: Value::Key($x), .. }) => Self::x( - state.take_or_fail(iter, "x: no unit")?, - state.take_or_fail(iter, "x: no content")?, + state.take_or_fail(token, "x: no unit")?, + state.take_or_fail(token, "x: no content")?, ), Some(Token { value: Value::Key($y), .. }) => Self::y( - state.take_or_fail(iter, "y: no unit")?, - state.take_or_fail(iter, "y: no content")?, + state.take_or_fail(token, "y: no unit")?, + state.take_or_fail(token, "y: no content")?, ), Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.take_or_fail(iter, "xy: no unit x")?, - state.take_or_fail(iter, "xy: no unit y")?, - state.take_or_fail(iter, "xy: no content")? + state.take_or_fail(token, "xy: no unit x")?, + state.take_or_fail(token, "xy: no unit y")?, + state.take_or_fail(token, "xy: no content")? ), _ => unreachable!(), }) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index e518462..95f7a42 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -149,9 +149,9 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'source> ::tengri::dsl::Namespace<'source, #state> for #command_enum { - fn take_from <'state> ( - state: &'state #state, + impl ::tengri::dsl::Namespace<#state> for #command_enum { + fn take_from <'source> ( + state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { let mut words = words.clone(); diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index f3940e1..bf951b5 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,9 +85,9 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl<'source> ::tengri::dsl::Namespace<'source, #state> for #t { - fn take_from <'state> ( - state: &'state #state, + impl ::tengri::dsl::Namespace<#state> for #t { + fn take_from <'source> ( + state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { Ok(Some(match words.next().map(|x|x.value) { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 4e6eeb2..7223dc0 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -43,10 +43,15 @@ 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 builtins = builtins().iter().map(|ty|write_quote(quote! { + if let Some(value) = Namespace::<#ty>::take_from(state, &mut exp.clone())? { + return Ok(Some(value.boxed())) + } + })).collect::>(); let mut available = vec![]; let exposed: Vec<_> = exposed.iter().map(|(key, value)|{ available.push(key.clone()); - write_quote(quote! { #key => Some(state.#value().boxed()), }) + write_quote(quote! { #key => Some(#view::#value(state).boxed()), }) }).collect(); let available: String = available.join(", "); let error_msg = LitStr::new( @@ -70,23 +75,26 @@ impl ToTokens for ViewDef { /// Gives [#view] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#view]'s state into consideration. - impl<'source> ::tengri::dsl::Namespace<'source, #view> for Box + 'source> { - fn take_from <'state> ( - state: &'state #view, - words: &mut ::tengri::dsl::TokenIter<'source>, + impl ::tengri::dsl::Namespace<#view> for Box> { + fn take_from <'source> ( + state: &#view, + words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { - Ok(words.peek().and_then(|::tengri::dsl::Token{ value, .. }|match value { - ::tengri::dsl::Value::Exp(_, exp) => { - todo!("builtin layout ops"); - //#builtins - None - }, - ::tengri::dsl::Value::Sym(sym) => match sym { - #(#exposed)* + Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { + match value { + ::tengri::dsl::Value::Exp(_, exp) => { + //#(#builtins)* + None + }, + ::tengri::dsl::Value::Sym(sym) => match sym { + #(#exposed)* + _ => None + }, _ => None - }, - _ => None - })) + } + } else { + None + }) } } }) @@ -95,19 +103,19 @@ impl ToTokens for ViewDef { fn builtins () -> [TokenStream2;14] { [ - quote! { When:: }, - quote! { Either:: }, - quote! { Align:: }, - quote! { Bsp:: }, - quote! { Fill:: }, - quote! { Fixed::<_, A> }, - quote! { Min::<_, A> }, - quote! { Max::<_, A> }, - quote! { Shrink::<_, A> }, - quote! { Expand::<_, A> }, - quote! { Push::<_, A> }, - quote! { Pull::<_, A> }, - quote! { Margin::<_, A> }, - quote! { Padding::<_, A> }, + quote! { When::<_> }, + quote! { Either::<_, _> }, + quote! { Align::<_> }, + quote! { Bsp::<_, _> }, + quote! { Fill::<_> }, + quote! { Fixed::<_, _> }, + quote! { Min::<_, _> }, + quote! { Max::<_, _> }, + quote! { Shrink::<_, _> }, + quote! { Expand::<_, _> }, + quote! { Push::<_, _> }, + quote! { Pull::<_, _> }, + quote! { Margin::<_, _> }, + quote! { Padding::<_, _> }, ] } diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 0d563fd..1b9cc1b 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -12,6 +12,18 @@ macro_rules! impl_content_layout_render { } } +mod tui_border; pub use self::tui_border::*; +mod tui_button; pub use self::tui_button::*; +mod tui_color; pub use self::tui_color::*; +mod tui_field; pub use self::tui_field::*; +mod tui_phat; pub use self::tui_phat::*; +mod tui_repeat; pub use self::tui_repeat::*; +mod tui_number; pub use self::tui_number::*; +mod tui_scroll; pub use self::tui_scroll::*; +mod tui_string; pub use self::tui_string::*; +mod tui_style; pub use self::tui_style::*; +mod tui_tryptich; pub use self::tui_tryptich::*; + impl> Content for std::sync::Arc { fn layout (&self, to: [u16;4]) -> [u16;4] { Content::::layout(&**self, to) @@ -44,14 +56,3 @@ impl> Content for Result Date: Wed, 21 May 2025 13:57:03 +0300 Subject: [PATCH 089/178] proc, view: fix usage of builtins --- output/src/ops.rs | 5 +- output/src/ops/either.rs | 38 ++++++++++++++ output/src/ops/{cond.rs => when.rs} | 37 ------------- proc/src/proc_view.rs | 81 +++++++++++++++-------------- 4 files changed, 83 insertions(+), 78 deletions(-) create mode 100644 output/src/ops/either.rs rename output/src/ops/{cond.rs => when.rs} (51%) diff --git a/output/src/ops.rs b/output/src/ops.rs index 7eba655..7b8010e 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -1,9 +1,10 @@ +//mod reduce; pub use self::reduce::*; mod align; pub use self::align::*; mod bsp; pub use self::bsp::*; -mod cond; pub use self::cond::*; +mod either; pub use self::either::*; mod map; pub use self::map::*; mod memo; pub use self::memo::*; mod stack; pub use self::stack::*; -//mod reduce; pub use self::reduce::*; mod thunk; pub use self::thunk::*; mod transform; pub use self::transform::*; +mod when; pub use self::when::*; diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs new file mode 100644 index 0000000..75c6ccf --- /dev/null +++ b/output/src/ops/either.rs @@ -0,0 +1,38 @@ +use crate::*; + +/// Show one item if a condition is true and another if the condition is false +pub struct Either(pub bool, pub A, pub B); +impl Either { + /// Create a ternary condition. + pub const fn new (c: bool, a: A, b: B) -> Self { + Self(c, a, b) + } +} +#[cfg(feature = "dsl")] +impl + Dsl + Dsl> Namespace for Either { + fn take_from <'source> ( + state: &T, + token: &mut TokenIter<'source> + ) -> Perhaps { + if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { + let base = token.clone(); + let _ = token.next().unwrap(); + return Ok(Some(Self( + state.take_or_fail(token, "either: no condition")?, + state.take_or_fail(token, "either: no content 1")?, + state.take_or_fail(token, "either: no content 2")?, + ))) + } + Ok(None) + } +} +impl, B: Render> Content for Either { + fn layout (&self, to: E::Area) -> E::Area { + let Self(cond, a, b) = self; + if *cond { a.layout(to) } else { b.layout(to) } + } + fn render (&self, to: &mut E) { + let Self(cond, a, b) = self; + if *cond { a.render(to) } else { b.render(to) } + } +} diff --git a/output/src/ops/cond.rs b/output/src/ops/when.rs similarity index 51% rename from output/src/ops/cond.rs rename to output/src/ops/when.rs index be950fe..188a5b5 100644 --- a/output/src/ops/cond.rs +++ b/output/src/ops/when.rs @@ -8,15 +8,6 @@ impl When { Self(c, a) } } - -/// Show one item if a condition is true and another if the condition is false -pub struct Either(pub bool, pub A, pub B); -impl Either { - /// Create a ternary condition. - pub const fn new (c: bool, a: A, b: B) -> Self { - Self(c, a, b) - } -} #[cfg(feature = "dsl")] impl + Dsl> Namespace for When { fn take_from <'source> ( @@ -55,31 +46,3 @@ impl> Content for When { if *cond { item.render(to) } } } -#[cfg(feature = "dsl")] -impl + Dsl + Dsl> Namespace for Either { - fn take_from <'source> ( - state: &T, - token: &mut TokenIter<'source> - ) -> Perhaps { - if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { - let base = token.clone(); - let _ = token.next().unwrap(); - return Ok(Some(Self( - state.take_or_fail(token, "either: no condition")?, - state.take_or_fail(token, "either: no content 1")?, - state.take_or_fail(token, "either: no content 2")?, - ))) - } - Ok(None) - } -} -impl, B: Render> Content for Either { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, a, b) = self; - if *cond { a.layout(to) } else { b.layout(to) } - } - fn render (&self, to: &mut E) { - let Self(cond, a, b) = self; - if *cond { a.render(to) } else { b.render(to) } - } -} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 7223dc0..ac18a80 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -42,50 +42,53 @@ 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 builtins = builtins().iter().map(|ty|write_quote(quote! { - if let Some(value) = Namespace::<#ty>::take_from(state, &mut exp.clone())? { - return Ok(Some(value.boxed())) - } - })).collect::>(); - let mut available = vec![]; - let exposed: Vec<_> = exposed.iter().map(|(key, value)|{ - available.push(key.clone()); - write_quote(quote! { #key => Some(#view::#value(state).boxed()), }) - }).collect(); - let available: String = available.join(", "); - let error_msg = LitStr::new( - &format!("expected Sym(content), got: {{token:?}}, available: {available}"), - Span::call_site() - ); + let self_ty = &block.self_ty; + let builtins: Vec<_> = builtins_with_types() + .iter() + .map(|ty|write_quote(quote! { + let value: Option<#ty> = Dsl::take(state, &mut exp.clone())?; + if let Some(value) = value { + return Ok(Some(value.boxed())) + } + })) + .collect(); + let exposed: Vec<_> = exposed + .iter() + .map(|(key, value)|write_quote(quote! { + #key => Some(#self_ty::#value(state).boxed()), + })).collect(); write_quote_to(out, quote! { #block /// Generated by [tengri_proc]. /// - /// Delegates the rendering of [#view] to the [#view::view} method, + /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, /// which you will need to implement, e.g. passing a [TokenIter] /// containing a layout and keybindings config from user dirs. - impl ::tengri::output::Content<#output> for #view { + impl ::tengri::output::Content<#output> for #self_ty { fn content (&self) -> impl Render<#output> { - self.view() + #self_ty::view(self) } } /// Generated by [tengri_proc]. /// - /// Gives [#view] the ability to construct the [Render]able + /// Gives [#self_ty] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], - /// while taking [#view]'s state into consideration. - impl ::tengri::dsl::Namespace<#view> for Box> { + /// while taking [#self_ty]'s state into consideration. + impl ::tengri::dsl::Namespace<#self_ty> for Box + '_> { fn take_from <'source> ( - state: &#view, + state: &#self_ty, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { match value { + // Expressions are handled by built-in functions + // that operate over constants and symbols. ::tengri::dsl::Value::Exp(_, exp) => { - //#(#builtins)* + #(#builtins)* None }, + // Symbols are handled by user-provided functions + // that take no parameters but `&self`. ::tengri::dsl::Value::Sym(sym) => match sym { #(#exposed)* _ => None @@ -101,21 +104,21 @@ impl ToTokens for ViewDef { } } -fn builtins () -> [TokenStream2;14] { +fn builtins_with_types () -> [TokenStream2;14] { [ - quote! { When::<_> }, - quote! { Either::<_, _> }, - quote! { Align::<_> }, - quote! { Bsp::<_, _> }, - quote! { Fill::<_> }, - quote! { Fixed::<_, _> }, - quote! { Min::<_, _> }, - quote! { Max::<_, _> }, - quote! { Shrink::<_, _> }, - quote! { Expand::<_, _> }, - quote! { Push::<_, _> }, - quote! { Pull::<_, _> }, - quote! { Margin::<_, _> }, - quote! { Padding::<_, _> }, + quote! { When< Box + '_> > }, + quote! { Either< Box + '_>, Box + '_>> }, + quote! { Align< Box + '_> > }, + quote! { Bsp< Box + '_>, Box + '_>> }, + quote! { Fill< Box + '_> > }, + quote! { Fixed<_, Box + '_> > }, + quote! { Min<_, Box + '_> > }, + quote! { Max<_, Box + '_> > }, + quote! { Shrink<_, Box + '_> > }, + quote! { Expand<_, Box + '_> > }, + quote! { Push<_, Box + '_> > }, + quote! { Pull<_, Box + '_> > }, + quote! { Margin<_, Box + '_> > }, + quote! { Padding<_, Box + '_> > }, ] } From daef8dfa9e1bc46a8a468ce4818f877fe5d49429 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 14:10:40 +0300 Subject: [PATCH 090/178] dsl: spiffier notfounds --- dsl/src/dsl_provide.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index cbd99ff..639fddf 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,34 +1,34 @@ use crate::*; pub trait Dsl { - fn take <'source> (&self, token: &mut TokenIter<'source>) -> Perhaps; + fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; fn take_or_fail <'source> ( - &self, token: &mut TokenIter<'source>, error: impl Into> + &self, words: &mut TokenIter<'source>, error: impl Into> ) -> Usually { - if let Some(value) = Dsl::::take(self, token)? { + if let Some(value) = Dsl::::take(self, words)? { Ok(value) } else { - Result::Err(error.into()) + Result::Err(format!("{}: {:?}", error.into(), words.peek().map(|x|x.value)).into()) } } } impl, State> Dsl for State { - fn take <'source> (&self, token: &mut TokenIter<'source>) -> Perhaps { - Namespace::take_from(self, token) + fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { + Namespace::take_from(self, words) } } pub trait Namespace: Sized { - fn take_from <'source> (state: &State, token: &mut TokenIter<'source>) + fn take_from <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; fn take_from_or_fail <'source> ( - state: &State, token: &mut TokenIter<'source>, error: impl Into> + state: &State, words: &mut TokenIter<'source>, error: impl Into> ) -> Usually { - if let Some(value) = Namespace::::take_from(state, token)? { + if let Some(value) = Namespace::::take_from(state, words)? { Ok(value) } else { - Result::Err(error.into()) + Result::Err(format!("{}: {:?}", error.into(), words.peek().map(|x|x.value)).into()) } } } From a4a1066f187e6b2e119e3c3b471219f1e128d4af Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 14:10:53 +0300 Subject: [PATCH 091/178] output: remove view; group tests --- output/src/lib.rs | 96 +++++++++++------------- output/src/ops/bsp.rs | 52 ++++++------- output/src/test.rs | 170 ++++++++++++++++++++++++++++++++++++++++++ output/src/view.rs | 54 -------------- 4 files changed, 237 insertions(+), 135 deletions(-) create mode 100644 output/src/test.rs delete mode 100644 output/src/view.rs diff --git a/output/src/lib.rs b/output/src/lib.rs index 2b12b04..6b2946a 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -1,63 +1,15 @@ #![feature(step_trait)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] -//#![feature(impl_trait_in_fn_trait_return)] - +pub(crate) use tengri_core::*; +pub(crate) use std::marker::PhantomData; +#[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::*; - -pub(crate) use tengri_core::*; -pub(crate) use std::marker::PhantomData; - -#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; -#[cfg(feature = "dsl")] mod view; -#[cfg(feature = "dsl")] pub use self::view::*; - -#[cfg(test)] use proptest_derive::Arbitrary; - -#[cfg(test)] #[test] fn test_stub_output () -> Usually<()> { - use crate::*; - struct TestOutput([u16;4]); - impl Output for TestOutput { - type Unit = u16; - type Size = [u16;2]; - type Area = [u16;4]; - fn area (&self) -> [u16;4] { - self.0 - } - fn area_mut (&mut self) -> &mut [u16;4] { - &mut self.0 - } - fn place (&mut self, _: [u16;4], _: &impl Render) { - () - } - } - impl Content for String { - fn render (&self, to: &mut TestOutput) { - to.area_mut().set_w(self.len() as u16); - } - } - Ok(()) -} - -#[cfg(test)] #[test] fn test_space () { - use crate::*; - assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); -} - -#[cfg(test)] #[test] fn test_iter_map () { - struct Foo; - impl Content for Foo {} - fn make_map + Send + Sync> (data: &Vec) -> impl Content { - Map::new(||data.iter(), |foo, index|{}) - } - let data = vec![Foo, Foo, Foo]; - //let map = make_map(&data); -} - #[cfg(test)] mod test { use crate::*; + use proptest_derive::Arbitrary; use proptest::{prelude::*, option::of}; proptest! { @@ -187,4 +139,44 @@ pub(crate) use std::marker::PhantomData; } } + #[test] fn test_stub_output () -> Usually<()> { + use crate::*; + struct TestOutput([u16;4]); + impl Output for TestOutput { + type Unit = u16; + type Size = [u16;2]; + type Area = [u16;4]; + fn area (&self) -> [u16;4] { + self.0 + } + fn area_mut (&mut self) -> &mut [u16;4] { + &mut self.0 + } + fn place (&mut self, _: [u16;4], _: &impl Render) { + () + } + } + impl Content for String { + fn render (&self, to: &mut TestOutput) { + to.area_mut().set_w(self.len() as u16); + } + } + Ok(()) + } + + #[test] fn test_space () { + use crate::*; + assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); + } + + #[test] fn test_iter_map () { + struct Foo; + impl Content for Foo {} + fn make_map + Send + Sync> (data: &Vec) -> impl Content { + Map::new(||data.iter(), |foo, index|{}) + } + let data = vec![Foo, Foo, Foo]; + //let map = make_map(&data); + } + } diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 6062485..8e1d8e9 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,35 +21,29 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -from_dsl!(@ab: Bsp: |state, iter|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.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/s"), .. }) => - Self::s(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/e"), .. }) => - Self::e(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/w"), .. }) => - Self::w(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/a"), .. }) => - Self::a(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - Some(Token { value: Value::Key("bsp/b"), .. }) => - Self::b(state.take_or_fail(iter, "expected content 1")?, - state.take_or_fail(iter, "expected content 2")?), - _ => unreachable!(), - })) -} else { - None -})); +impl + Dsl + std::fmt::Debug, A, B> Namespace for Bsp { + fn take_from <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + Ok(if let Some(Token { + value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), + .. + }) = words.peek() { + let base = words.clone(); + let a: A = state.take_or_fail(words, "expected content 1")?; + let b: B = state.take_or_fail(words, "expected content 2")?; + return Ok(Some(match words.next() { + Some(Token { value: Value::Key("bsp/n"), .. }) => Self::n(a, b), + Some(Token { value: Value::Key("bsp/s"), .. }) => Self::s(a, b), + Some(Token { value: Value::Key("bsp/e"), .. }) => Self::e(a, b), + Some(Token { value: Value::Key("bsp/w"), .. }) => Self::w(a, b), + Some(Token { value: Value::Key("bsp/a"), .. }) => Self::a(a, b), + Some(Token { value: Value::Key("bsp/b"), .. }) => Self::b(a, b), + _ => unreachable!(), + })) + } else { + 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/test.rs b/output/src/test.rs new file mode 100644 index 0000000..517aa64 --- /dev/null +++ b/output/src/test.rs @@ -0,0 +1,170 @@ +use crate::*; +use proptest_derive::Arbitrary; +use proptest::{prelude::*, option::of}; + +proptest! { + #[test] fn proptest_direction ( + d in prop_oneof![ + Just(North), Just(South), + Just(East), Just(West), + Just(Above), Just(Below) + ], + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + ) { + let _ = d.split_fixed([x, y, w, h], a); + } +} + +proptest! { + #[test] fn proptest_size ( + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + b in u16::MIN..u16::MAX, + ) { + let size = [x, y]; + let _ = size.w(); + let _ = size.h(); + let _ = size.wh(); + let _ = size.clip_w(a); + let _ = size.clip_h(b); + let _ = size.expect_min(a, b); + let _ = size.to_area_pos(); + let _ = size.to_area_size(); + } +} + +proptest! { + #[test] fn proptest_area ( + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + a in u16::MIN..u16::MAX, + b in u16::MIN..u16::MAX, + ) { + let _: [u16;4] = <[u16;4] as Area>::zero(); + let _: [u16;4] = <[u16;4] as Area>::from_position([a, b]); + let _: [u16;4] = <[u16;4] as Area>::from_size([a, b]); + let area: [u16;4] = [x, y, w, h]; + let _ = area.expect_min(a, b); + let _ = area.xy(); + let _ = area.wh(); + let _ = area.xywh(); + let _ = area.clip_h(a); + let _ = area.clip_w(b); + let _ = area.clip([a, b]); + let _ = area.set_w(a); + let _ = area.set_h(b); + let _ = area.x2(); + let _ = area.y2(); + let _ = area.lrtb(); + let _ = area.center(); + let _ = area.center_x(a); + let _ = area.center_y(b); + let _ = area.center_xy([a, b]); + let _ = area.centered(); + } +} + +macro_rules! test_op_transform { + ($fn:ident, $Op:ident) => { + proptest! { + #[test] fn $fn ( + op_x in of(u16::MIN..u16::MAX), + op_y in of(u16::MIN..u16::MAX), + content in "\\PC*", + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + ) { + if let Some(op) = match (op_x, op_y) { + (Some(x), Some(y)) => Some($Op::xy(x, y, content)), + (Some(x), None) => Some($Op::x(x, content)), + (Some(y), None) => Some($Op::y(y, content)), + _ => None + } { + assert_eq!(Content::layout(&op, [x, y, w, h]), + Render::layout(&op, [x, y, w, h])); + } + } + } + } +} + +test_op_transform!(proptest_op_fixed, Fixed); +test_op_transform!(proptest_op_min, Min); +test_op_transform!(proptest_op_max, Max); +test_op_transform!(proptest_op_push, Push); +test_op_transform!(proptest_op_pull, Pull); +test_op_transform!(proptest_op_shrink, Shrink); +test_op_transform!(proptest_op_expand, Expand); +test_op_transform!(proptest_op_margin, Margin); +test_op_transform!(proptest_op_padding, Padding); + +proptest! { + #[test] fn proptest_op_bsp ( + d in prop_oneof![ + Just(North), Just(South), + Just(East), Just(West), + Just(Above), Just(Below) + ], + a in "\\PC*", + b in "\\PC*", + x in u16::MIN..u16::MAX, + y in u16::MIN..u16::MAX, + w in u16::MIN..u16::MAX, + h in u16::MIN..u16::MAX, + ) { + let bsp = Bsp(d, a, b); + assert_eq!( + Content::layout(&bsp, [x, y, w, h]), + Render::layout(&bsp, [x, y, w, h]), + ); + } +} + +#[test] fn test_stub_output () -> Usually<()> { + use crate::*; + struct TestOutput([u16;4]); + impl Output for TestOutput { + type Unit = u16; + type Size = [u16;2]; + type Area = [u16;4]; + fn area (&self) -> [u16;4] { + self.0 + } + fn area_mut (&mut self) -> &mut [u16;4] { + &mut self.0 + } + fn place (&mut self, _: [u16;4], _: &impl Render) { + () + } + } + impl Content for String { + fn render (&self, to: &mut TestOutput) { + to.area_mut().set_w(self.len() as u16); + } + } + Ok(()) +} + +#[test] fn test_space () { + use crate::*; + assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); +} + +#[test] fn test_iter_map () { + struct Foo; + impl Content for Foo {} + fn make_map + Send + Sync> (data: &Vec) -> impl Content { + Map::new(||data.iter(), |foo, index|{}) + } + let data = vec![Foo, Foo, Foo]; + //let map = make_map(&data); +} diff --git a/output/src/view.rs b/output/src/view.rs deleted file mode 100644 index ae252d9..0000000 --- a/output/src/view.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::*; - -//#[cfg(feature = "dsl")] -//#[macro_export] macro_rules! try_delegate { - //($s:ident, $dsl:expr, $T:ty) => { - //let value: Option<$T> = Dsl::take_from($s, $dsl)?; - //if let Some(value) = value { - //return Ok(Some(value.boxed())) - //} - //} -//} - -//// Provides components to the view. -//#[cfg(feature = "dsl")] -//pub trait ViewContext<'state, E: Output + 'state>: - //Namespace + Namespace + Namespace + Send + Sync -//{ - //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) - //} -//} - -//#[cfg(feature = "dsl")] -//impl<'context, O: Output + 'context, T: ViewContext<'context, O>> Namespace for RenderBox<'context, O> { - //fn take_from <'state, 'source: 'state> (state: &'state T, token: &mut TokenIter<'source>) - //-> Perhaps> - //{ - //Ok(if let Some(content) = state.get_content_sym(token)? { - //Some(content) - //} else if let Some(content) = state.get_content_exp(token)? { - //Some(content) - //} else { - //None - //}) - //} -//} From abc87d3234a382b74228ce01ab250df875f8467d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 14:17:27 +0300 Subject: [PATCH 092/178] dsl, output: error handlers --- dsl/src/dsl_provide.rs | 12 +-- output/src/lib.rs | 174 +----------------------------------- output/src/ops/align.rs | 2 +- output/src/ops/bsp.rs | 4 +- output/src/ops/either.rs | 6 +- output/src/ops/transform.rs | 20 ++--- output/src/ops/when.rs | 4 +- 7 files changed, 25 insertions(+), 197 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 639fddf..c2f411f 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -2,13 +2,13 @@ use crate::*; pub trait Dsl { fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; - fn take_or_fail <'source> ( - &self, words: &mut TokenIter<'source>, error: impl Into> + fn take_or_fail <'source, E: Into>, F: Fn()->E> ( + &self, words: &mut TokenIter<'source>, error: F ) -> Usually { if let Some(value) = Dsl::::take(self, words)? { Ok(value) } else { - Result::Err(format!("{}: {:?}", error.into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("{}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } @@ -22,13 +22,13 @@ impl, State> Dsl for State { pub trait Namespace: Sized { fn take_from <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; - fn take_from_or_fail <'source> ( - state: &State, words: &mut TokenIter<'source>, error: impl Into> + fn take_from_or_fail <'source, E: Into>, F: Fn()->E> ( + state: &State, words: &mut TokenIter<'source>, error: F ) -> Usually { if let Some(value) = Namespace::::take_from(state, words)? { Ok(value) } else { - Result::Err(format!("{}: {:?}", error.into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("{}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } diff --git a/output/src/lib.rs b/output/src/lib.rs index 6b2946a..394f332 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -7,176 +7,4 @@ pub(crate) use std::marker::PhantomData; mod space; pub use self::space::*; mod ops; pub use self::ops::*; mod output; pub use self::output::*; -#[cfg(test)] mod test { - use crate::*; - use proptest_derive::Arbitrary; - use proptest::{prelude::*, option::of}; - - proptest! { - #[test] fn proptest_direction ( - d in prop_oneof![ - Just(North), Just(South), - Just(East), Just(West), - Just(Above), Just(Below) - ], - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - ) { - let _ = d.split_fixed([x, y, w, h], a); - } - } - - proptest! { - #[test] fn proptest_size ( - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - b in u16::MIN..u16::MAX, - ) { - let size = [x, y]; - let _ = size.w(); - let _ = size.h(); - let _ = size.wh(); - let _ = size.clip_w(a); - let _ = size.clip_h(b); - let _ = size.expect_min(a, b); - let _ = size.to_area_pos(); - let _ = size.to_area_size(); - } - } - - proptest! { - #[test] fn proptest_area ( - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - a in u16::MIN..u16::MAX, - b in u16::MIN..u16::MAX, - ) { - let _: [u16;4] = <[u16;4] as Area>::zero(); - let _: [u16;4] = <[u16;4] as Area>::from_position([a, b]); - let _: [u16;4] = <[u16;4] as Area>::from_size([a, b]); - let area: [u16;4] = [x, y, w, h]; - let _ = area.expect_min(a, b); - let _ = area.xy(); - let _ = area.wh(); - let _ = area.xywh(); - let _ = area.clip_h(a); - let _ = area.clip_w(b); - let _ = area.clip([a, b]); - let _ = area.set_w(a); - let _ = area.set_h(b); - let _ = area.x2(); - let _ = area.y2(); - let _ = area.lrtb(); - let _ = area.center(); - let _ = area.center_x(a); - let _ = area.center_y(b); - let _ = area.center_xy([a, b]); - let _ = area.centered(); - } - } - - macro_rules! test_op_transform { - ($fn:ident, $Op:ident) => { - proptest! { - #[test] fn $fn ( - op_x in of(u16::MIN..u16::MAX), - op_y in of(u16::MIN..u16::MAX), - content in "\\PC*", - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - ) { - if let Some(op) = match (op_x, op_y) { - (Some(x), Some(y)) => Some($Op::xy(x, y, content)), - (Some(x), None) => Some($Op::x(x, content)), - (Some(y), None) => Some($Op::y(y, content)), - _ => None - } { - assert_eq!(Content::layout(&op, [x, y, w, h]), - Render::layout(&op, [x, y, w, h])); - } - } - } - } - } - - test_op_transform!(proptest_op_fixed, Fixed); - test_op_transform!(proptest_op_min, Min); - test_op_transform!(proptest_op_max, Max); - test_op_transform!(proptest_op_push, Push); - test_op_transform!(proptest_op_pull, Pull); - test_op_transform!(proptest_op_shrink, Shrink); - test_op_transform!(proptest_op_expand, Expand); - test_op_transform!(proptest_op_margin, Margin); - test_op_transform!(proptest_op_padding, Padding); - - proptest! { - #[test] fn proptest_op_bsp ( - d in prop_oneof![ - Just(North), Just(South), - Just(East), Just(West), - Just(Above), Just(Below) - ], - a in "\\PC*", - b in "\\PC*", - x in u16::MIN..u16::MAX, - y in u16::MIN..u16::MAX, - w in u16::MIN..u16::MAX, - h in u16::MIN..u16::MAX, - ) { - let bsp = Bsp(d, a, b); - assert_eq!( - Content::layout(&bsp, [x, y, w, h]), - Render::layout(&bsp, [x, y, w, h]), - ); - } - } - - #[test] fn test_stub_output () -> Usually<()> { - use crate::*; - struct TestOutput([u16;4]); - impl Output for TestOutput { - type Unit = u16; - type Size = [u16;2]; - type Area = [u16;4]; - fn area (&self) -> [u16;4] { - self.0 - } - fn area_mut (&mut self) -> &mut [u16;4] { - &mut self.0 - } - fn place (&mut self, _: [u16;4], _: &impl Render) { - () - } - } - impl Content for String { - fn render (&self, to: &mut TestOutput) { - to.area_mut().set_w(self.len() as u16); - } - } - Ok(()) - } - - #[test] fn test_space () { - use crate::*; - assert_eq!(Area::center(&[10u16, 10, 20, 20]), [20, 20]); - } - - #[test] fn test_iter_map () { - struct Foo; - impl Content for Foo {} - fn make_map + Send + Sync> (data: &Vec) -> impl Content { - Map::new(||data.iter(), |foo, index|{}) - } - let data = vec![Foo, Foo, Foo]; - //let map = make_map(&data); - } - -} +#[cfg(test)] mod test; diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 5702a40..0809a10 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -42,7 +42,7 @@ from_dsl!(@a: Align: |state, iter|if let Some(Token { value: Value::Key(key), "align/n"|"align/s"|"align/e"|"align/w"| "align/nw"|"align/sw"|"align/ne"|"align/se" => { let _ = iter.next().unwrap(); - let content = state.take_or_fail(&mut iter.clone(), "expected content")?; + let content = state.take_or_fail(&mut iter.clone(), ||"expected content")?; return Ok(Some(match key { "align/c" => Self::c(content), "align/x" => Self::x(content), diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index 8e1d8e9..41fd5e6 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -28,8 +28,8 @@ impl + Dsl + std::fmt::Debug, A, B> Namespace for Bsp Self::n(a, b), Some(Token { value: Value::Key("bsp/s"), .. }) => Self::s(a, b), diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs index 75c6ccf..b8e6ef9 100644 --- a/output/src/ops/either.rs +++ b/output/src/ops/either.rs @@ -18,9 +18,9 @@ impl + Dsl + Dsl> Namespace for Either { let base = token.clone(); let _ = token.next().unwrap(); return Ok(Some(Self( - state.take_or_fail(token, "either: no condition")?, - state.take_or_fail(token, "either: no content 1")?, - state.take_or_fail(token, "either: no content 2")?, + state.take_or_fail(token, ||"either: no condition")?, + state.take_or_fail(token, ||"either: no content 1")?, + state.take_or_fail(token, ||"either: no content 2")?, ))) } Ok(None) diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 776d95c..9719c9a 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -41,11 +41,11 @@ macro_rules! transform_xy { let mut base = token.clone(); return Ok(Some(match token.next() { Some(Token{value:Value::Key($x),..}) => - Self::x(state.take_or_fail(token, "x: no content")?), + Self::x(state.take_or_fail(token, ||"x: no content")?), Some(Token{value:Value::Key($y),..}) => - Self::y(state.take_or_fail(token, "y: no content")?), + Self::y(state.take_or_fail(token, ||"y: no content")?), Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.take_or_fail(token, "xy: no content")?), + Self::xy(state.take_or_fail(token, ||"xy: no content")?), _ => unreachable!() })) } @@ -87,17 +87,17 @@ macro_rules! transform_xy_unit { let mut base = token.clone(); Some(match token.next() { Some(Token { value: Value::Key($x), .. }) => Self::x( - state.take_or_fail(token, "x: no unit")?, - state.take_or_fail(token, "x: no content")?, + state.take_or_fail(token, ||"x: no unit")?, + state.take_or_fail(token, ||"x: no content")?, ), Some(Token { value: Value::Key($y), .. }) => Self::y( - state.take_or_fail(token, "y: no unit")?, - state.take_or_fail(token, "y: no content")?, + state.take_or_fail(token, ||"y: no unit")?, + state.take_or_fail(token, ||"y: no content")?, ), Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.take_or_fail(token, "xy: no unit x")?, - state.take_or_fail(token, "xy: no unit y")?, - state.take_or_fail(token, "xy: no content")? + state.take_or_fail(token, ||"xy: no unit x")?, + state.take_or_fail(token, ||"xy: no unit y")?, + state.take_or_fail(token, ||"xy: no content")? ), _ => unreachable!(), }) diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs index 188a5b5..35f1910 100644 --- a/output/src/ops/when.rs +++ b/output/src/ops/when.rs @@ -20,8 +20,8 @@ impl + Dsl> Namespace for When { }) = token.peek() { let base = token.clone(); return Ok(Some(Self( - state.take_or_fail(token, "cond: no condition")?, - state.take_or_fail(token, "cond: no content")?, + state.take_or_fail(token, ||"cond: no condition")?, + state.take_or_fail(token, ||"cond: no content")?, ))) } else { None From 583660c330722028f2ebbf10eadd35ae67fe64bc Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 21 May 2025 15:54:25 +0300 Subject: [PATCH 093/178] wip: finally, informative type errors from the macro fixin mixin --- dsl/src/dsl_provide.rs | 98 ++++++++++--------------------------- input/src/input_dsl.rs | 63 ++++++++++-------------- output/src/ops/align.rs | 55 +++++++++++---------- output/src/ops/bsp.rs | 32 ++++++------ output/src/ops/either.rs | 13 ++--- output/src/ops/transform.rs | 32 ++++++------ output/src/ops/when.rs | 16 +++--- proc/src/proc_command.rs | 6 +-- proc/src/proc_expose.rs | 4 +- proc/src/proc_view.rs | 38 +++++++------- 10 files changed, 152 insertions(+), 205 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index c2f411f..66e18d8 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,114 +1,70 @@ use crate::*; -pub trait Dsl { - fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; - fn take_or_fail <'source, E: Into>, F: Fn()->E> ( +pub trait Receive { + fn receive <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; + fn receive_or_fail <'source, E: Into>, F: Fn()->E> ( &self, words: &mut TokenIter<'source>, error: F ) -> Usually { - if let Some(value) = Dsl::::take(self, words)? { + if let Some(value) = Receive::::receive(self, words)? { Ok(value) } else { - Result::Err(format!("{}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("receive: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } -impl, State> Dsl for State { - fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { - Namespace::take_from(self, words) - } -} - -pub trait Namespace: Sized { - fn take_from <'source> (state: &State, words: &mut TokenIter<'source>) +pub trait Provide<'n, State>: Sized + 'n { + fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; - fn take_from_or_fail <'source, E: Into>, F: Fn()->E> ( + fn provide_or_fail <'source, E: Into>, F: Fn()->E> ( state: &State, words: &mut TokenIter<'source>, error: F ) -> Usually { - if let Some(value) = Namespace::::take_from(state, words)? { + if let Some(value) = Provide::::provide(state, words)? { Ok(value) } else { - Result::Err(format!("{}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("provide: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } -//impl, T> Namespace for T { - //fn take_from <'state, 'source: 'state> (state: &'state State, token: &mut TokenIter<'source>) - //-> Perhaps - //{ - //Dsl::take(state, token) - //} -//} - -/// Implement the [Dsl] trait, which boils down to +/// Implement the [Receive] trait, which boils down to /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl, A> Namespace for $T { - fn take_from <'source> ( - $state: &State, - $words: &mut TokenIter<'source>, - ) -> Perhaps<$T> { + impl<'n, State, A: Provide<'n, State>> Provide<'n, State> for $T { + fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl + Dsl, A, B> Namespace for $T { - fn take_from <'source> ( - $state: &State, - $words: &mut TokenIter<'source>, - ) -> Perhaps<$T> { + impl<'n, State, A: Provide<'n, State>, B: Provide<'n, State>> Provide<'n, State> for $T { + fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl Namespace<$S> for $T { - fn take_from <'source> ( - $state: &$S, - $words: &mut TokenIter<'source>, - ) -> Perhaps<$T> { + impl<'n> Provide<'n, $S> for $T { + fn provide <'source> ($state: &$S, $words: &mut TokenIter<'source>) -> 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!() - //} - //fn take_or_fail <'state, 'source> ( - //&'state self, - //token: &mut TokenIter<'source>, - //error: impl Into> - //) -> Usually { - //if let Some(value) = Dsl::::take(self, token)? { - //Ok(value) - //} else { - //Result::Err(error.into()) - //} +// auto impl graveyard: + +//impl<'n, State: Receive, Type: 'n> Provide<'n, State> for Type { + //fn provide <'source> (state: &State, words: &mut TokenIter<'source>) + //-> Perhaps + //{ + //state.take(words) //} //} -//impl, U> Dsl for &T { - //fn take <'state, 'source> (&'state self, iter: &mut TokenIter<'source>) -> Perhaps { - //(*self).take(iter) +//impl<'n, Type: Provide<'n, State>, State> Receive for State { + //fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { + //Type::provide(self, words) //} //} - -//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: Dsl<'state, X> { -//} - -//impl> Dsl for T { -//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 046417a..5df10cb 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,15 +5,13 @@ use std::marker::PhantomData; pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. -pub trait KeyMap<'source, S, C: Namespace + Command, I: DslInput> { +pub trait KeyMap<'k, S, C: Provide<'k, S> + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'source, S, C: Namespace + Command, I: DslInput> -KeyMap<'source, S, C, I> -for SourceIter<'source> { +impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for SourceIter<'k> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { @@ -24,7 +22,7 @@ for SourceIter<'source> { match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Namespace::take_from(state, &mut exp_iter)? { + if let Some(command) = Provide::provide(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -40,9 +38,7 @@ for SourceIter<'source> { } /// A [TokenIter] can be a [KeyMap]. -impl<'source, S, C: Namespace + Command, I: DslInput> -KeyMap<'source, S, C, I> -for TokenIter<'source> { +impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for TokenIter<'k> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { @@ -52,7 +48,7 @@ for TokenIter<'source> { match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Namespace::take_from(state, &mut e)? { + if let Some(command) = Provide::provide(state, &mut e)? { return Ok(Some(command)) } } @@ -67,38 +63,33 @@ for TokenIter<'source> { } } -pub type InputCondition<'source, S> = - BoxUsually + Send + Sync + 'source>; +pub type InputCondition<'k, S> = BoxUsually + Send + Sync + 'k>; /// A collection of pre-configured mappings of input events to commands, /// which may be made available subject to given conditions. -pub struct InputMap<'source, +pub struct InputMap<'k, S, - C: Command + Namespace, + C: Command + Provide<'k, S>, I: DslInput, - M: KeyMap<'source, S, C, I> + M: KeyMap<'k, S, C, I> > { - __: PhantomData<&'source (S, C, I)>, - pub layers: Vec<(InputCondition<'source, S>, M)>, + __: PhantomData<&'k (S, C, I)>, + pub layers: Vec<(InputCondition<'k, S>, M)>, } -impl<'source, +impl<'k, S, - C: Command + Namespace, + C: Command + Provide<'k, S>, I: DslInput, - M: KeyMap<'source, S, C, I> -> Default for InputMap<'source, S, C, I, M>{ + M: KeyMap<'k, S, C, I> +> Default for InputMap<'k, S, C, I, M>{ fn default () -> Self { Self { __: PhantomData, layers: vec![] } } } -impl<'source, - S, - C: Command + Namespace, - I: DslInput, - M: KeyMap<'source, S, C, I> -> InputMap<'source, S, C, I, M> { +impl<'k, S, C: Command + Provide<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> +InputMap<'k, S, C, I, M> { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) } @@ -110,34 +101,30 @@ impl<'source, self.add_layer_if(Box::new(|_|Ok(true)), keymap); self } - pub fn layer_if (mut self, condition: InputCondition<'source, S>, keymap: M) -> Self { + pub fn layer_if (mut self, condition: InputCondition<'k, S>, keymap: M) -> Self { self.add_layer_if(condition, keymap); self } - pub fn add_layer_if (&mut self, condition: InputCondition<'source, S>, keymap: M) -> &mut Self { + pub fn add_layer_if (&mut self, condition: InputCondition<'k, S>, keymap: M) -> &mut Self { self.layers.push((Box::new(condition), keymap)); self } } -impl<'source, - S, - C: Command + Namespace, - I: DslInput, - M: KeyMap<'source, S, C, I> -> std::fmt::Debug for InputMap<'source, S, C, I, M> { +impl<'k, S, C: Command + Provide<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> +std::fmt::Debug for InputMap<'k, S, C, I, M> { 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<'source, +impl<'k, S, - C: Command + Namespace, + C: Command + Provide<'k, S>, I: DslInput, - M: KeyMap<'source, S, C, I> -> KeyMap<'source, S, C, I> for InputMap<'source, S, C, I, M> { + M: KeyMap<'k, S, C, I> +> KeyMap<'k, S, C, I> for InputMap<'k, S, C, I, M> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { for (condition, keymap) in self.layers.iter() { if !condition(state)? { diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 0809a10..ee168ea 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,33 +36,36 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -from_dsl!(@a: 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 = state.take_or_fail(&mut iter.clone(), ||"expected content")?; - 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) +impl<'n, State: Receive, A: 'n> Provide<'n, State> for Align { + fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + if let Some(Token { value: Value::Key(key), .. }) = words.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 _ = words.next().unwrap(); + let content = state.receive_or_fail(&mut words.clone(), ||"expected content")?; + 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!() + })) + }, + _ => {} + } + } + Ok(None) } -} else { - Ok(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 41fd5e6..cb38197 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,24 +21,28 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -impl + Dsl + std::fmt::Debug, A, B> Namespace for Bsp { - fn take_from <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { +impl<'n, State: Receive + Receive, A: 'n, B: 'n> Provide<'n, State> for Bsp { + fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), .. }) = words.peek() { - let base = words.clone(); - let a: A = state.take_or_fail(words, ||"expected content 1")?; - let b: B = state.take_or_fail(words, ||"expected content 2")?; - return Ok(Some(match words.next() { - Some(Token { value: Value::Key("bsp/n"), .. }) => Self::n(a, b), - Some(Token { value: Value::Key("bsp/s"), .. }) => Self::s(a, b), - Some(Token { value: Value::Key("bsp/e"), .. }) => Self::e(a, b), - Some(Token { value: Value::Key("bsp/w"), .. }) => Self::w(a, b), - Some(Token { value: Value::Key("bsp/a"), .. }) => Self::a(a, b), - Some(Token { value: Value::Key("bsp/b"), .. }) => Self::b(a, b), - _ => unreachable!(), - })) + if let Value::Key(key) = words.next().unwrap().value() { + let base = words.clone(); + let a: A = state.receive_or_fail(words, ||"bsp: expected content 1")?; + let b: B = state.receive_or_fail(words, ||"bsp: expected content 2")?; + return Ok(Some(match key { + "bsp/n" => Self::n(a, b), + "bsp/s" => Self::s(a, b), + "bsp/e" => Self::e(a, b), + "bsp/w" => Self::w(a, b), + "bsp/a" => Self::a(a, b), + "bsp/b" => Self::b(a, b), + _ => unreachable!(), + })) + } else { + unreachable!() + } } else { None }) diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs index b8e6ef9..d3813c1 100644 --- a/output/src/ops/either.rs +++ b/output/src/ops/either.rs @@ -9,18 +9,15 @@ impl Either { } } #[cfg(feature = "dsl")] -impl + Dsl + Dsl> Namespace for Either { - fn take_from <'source> ( - state: &T, - token: &mut TokenIter<'source> - ) -> Perhaps { +impl<'n, State: Receive + Receive + Receive, A: 'n, B: 'n> Provide<'n, State> for Either { + fn provide <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { let base = token.clone(); let _ = token.next().unwrap(); return Ok(Some(Self( - state.take_or_fail(token, ||"either: no condition")?, - state.take_or_fail(token, ||"either: no content 1")?, - state.take_or_fail(token, ||"either: no content 2")?, + state.receive_or_fail(token, ||"either: no condition")?, + state.receive_or_fail(token, ||"either: no content 1")?, + state.receive_or_fail(token, ||"either: no content 2")?, ))) } Ok(None) diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 9719c9a..2715bde 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,19 +33,19 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl> Namespace for $Enum { - fn take_from <'source> ( - state: &T, token: &mut TokenIter<'source> - ) -> Perhaps { + impl<'n, A: 'n, T: Receive> Provide<'n, T> for $Enum { + fn provide <'source> (state: &T, token: &mut TokenIter<'source>) + -> Perhaps + { if let Some(Token { value: Value::Key(k), .. }) = token.peek() { let mut base = token.clone(); return Ok(Some(match token.next() { Some(Token{value:Value::Key($x),..}) => - Self::x(state.take_or_fail(token, ||"x: no content")?), + Self::x(state.receive_or_fail(token, ||"x: no content")?), Some(Token{value:Value::Key($y),..}) => - Self::y(state.take_or_fail(token, ||"y: no content")?), + Self::y(state.receive_or_fail(token, ||"y: no content")?), Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.take_or_fail(token, ||"xy: no content")?), + Self::xy(state.receive_or_fail(token, ||"xy: no content")?), _ => unreachable!() })) } @@ -79,25 +79,25 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl + Dsl> Namespace for $Enum { - fn take_from <'source> ( + impl<'n, A: 'n, U: Coordinate + 'n, T: Receive + Receive> Provide<'n, T> for $Enum { + fn provide <'source> ( state: &T, token: &mut TokenIter<'source> ) -> Perhaps { Ok(if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = token.peek() { let mut base = token.clone(); Some(match token.next() { Some(Token { value: Value::Key($x), .. }) => Self::x( - state.take_or_fail(token, ||"x: no unit")?, - state.take_or_fail(token, ||"x: no content")?, + state.receive_or_fail(token, ||"x: no unit")?, + state.receive_or_fail(token, ||"x: no content")?, ), Some(Token { value: Value::Key($y), .. }) => Self::y( - state.take_or_fail(token, ||"y: no unit")?, - state.take_or_fail(token, ||"y: no content")?, + state.receive_or_fail(token, ||"y: no unit")?, + state.receive_or_fail(token, ||"y: no content")?, ), Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.take_or_fail(token, ||"xy: no unit x")?, - state.take_or_fail(token, ||"xy: no unit y")?, - state.take_or_fail(token, ||"xy: no content")? + state.receive_or_fail(token, ||"xy: no unit x")?, + state.receive_or_fail(token, ||"xy: no unit y")?, + state.receive_or_fail(token, ||"xy: no content")? ), _ => unreachable!(), }) diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs index 35f1910..c2c0b1a 100644 --- a/output/src/ops/when.rs +++ b/output/src/ops/when.rs @@ -9,19 +9,17 @@ impl When { } } #[cfg(feature = "dsl")] -impl + Dsl> Namespace for When { - fn take_from <'source> ( - state: &T, - token: &mut TokenIter<'source> - ) -> Perhaps { +impl<'n, State: Receive + Receive, A: 'n> Provide<'n, State> for When { + fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("when"), .. - }) = token.peek() { - let base = token.clone(); + }) = words.peek() { + let _ = words.next(); + let base = words.clone(); return Ok(Some(Self( - state.take_or_fail(token, ||"cond: no condition")?, - state.take_or_fail(token, ||"cond: no content")?, + state.receive_or_fail(words, ||"cond: no condition")?, + state.receive_or_fail(words, ||"cond: no content")?, ))) } else { None diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 95f7a42..e16146d 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: Namespace::take_from_or_fail(self, words)?, + #arg: Provide::provide_or_fail(self, words)?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::Namespace<#state> for #command_enum { - fn take_from <'source> ( + impl<'n> ::tengri::dsl::Provide<'n, #state> for #command_enum { + fn provide <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index bf951b5..6911a40 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,8 +85,8 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::Namespace<#state> for #t { - fn take_from <'source> ( + impl<'n> ::tengri::dsl::Provide<'n, #state> for #t { + fn provide <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index ac18a80..a8d8f7a 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -46,7 +46,7 @@ impl ToTokens for ViewDef { let builtins: Vec<_> = builtins_with_types() .iter() .map(|ty|write_quote(quote! { - let value: Option<#ty> = Dsl::take(state, &mut exp.clone())?; + let value: Option<#ty> = Receive::<#ty>::receive(self, &mut exp.clone())?; if let Some(value) = value { return Ok(Some(value.boxed())) } @@ -55,30 +55,20 @@ impl ToTokens for ViewDef { let exposed: Vec<_> = exposed .iter() .map(|(key, value)|write_quote(quote! { - #key => Some(#self_ty::#value(state).boxed()), + #key => { + Some(Box::new(Thunk::new(move||#self_ty::#value(self))))//Box::new("todo")) + }, })).collect(); write_quote_to(out, quote! { - #block - /// Generated by [tengri_proc]. - /// - /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, - /// which you will need to implement, e.g. passing a [TokenIter] - /// containing a layout and keybindings config from user dirs. - impl ::tengri::output::Content<#output> for #self_ty { - fn content (&self) -> impl Render<#output> { - #self_ty::view(self) - } - } /// Generated by [tengri_proc]. /// /// Gives [#self_ty] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl ::tengri::dsl::Namespace<#self_ty> for Box + '_> { - fn take_from <'source> ( - state: &#self_ty, - words: &mut ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { + impl ::tengri::dsl::Receive>> for #self_ty { + fn receive <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) + -> Perhaps>> + { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { match value { // Expressions are handled by built-in functions @@ -100,6 +90,18 @@ impl ToTokens for ViewDef { }) } } + /// Generated by [tengri_proc]. + /// + /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, + /// which you will need to implement, e.g. passing a [TokenIter] + /// containing a layout and keybindings config from user dirs. + impl ::tengri::output::Content<#output> for #self_ty { + fn content (&self) -> impl Render<#output> + '_ { + #self_ty::view(self) + } + } + // Original user-provided implementation: + #block }) } } From ddf0c05d5fc4a9be177480742f93f2bdbae81040 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 23 May 2025 21:39:29 +0300 Subject: [PATCH 094/178] dsl: Provide -> Take, Receive -> Give (swap + shorten) --- dsl/src/dsl_provide.rs | 36 +++++++++++++++++++++--------------- input/src/input_dsl.rs | 20 ++++++++++---------- output/src/ops/align.rs | 4 ++-- output/src/ops/bsp.rs | 6 +++--- output/src/ops/either.rs | 8 ++++---- output/src/ops/transform.rs | 24 ++++++++++++------------ output/src/ops/when.rs | 9 ++++++--- proc/src/proc_command.rs | 4 ++-- proc/src/proc_expose.rs | 2 +- proc/src/proc_view.rs | 6 +++--- 10 files changed, 64 insertions(+), 55 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 66e18d8..bda4381 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,25 +1,31 @@ use crate::*; -pub trait Receive { - fn receive <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; - fn receive_or_fail <'source, E: Into>, F: Fn()->E> ( +// maybe their names should be switched around? + +/// [Take]s instances of [Type] given [TokenIter]. +pub trait Give { + /// Implement this to be able to [Give] [Type] from the [TokenIter]. + fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; + /// Return custom error on [None]. + fn give_or_fail <'source, E: Into>, F: Fn()->E> ( &self, words: &mut TokenIter<'source>, error: F ) -> Usually { - if let Some(value) = Receive::::receive(self, words)? { + if let Some(value) = Give::::give(self, words)? { Ok(value) } else { - Result::Err(format!("receive: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("give: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } -pub trait Provide<'n, State>: Sized + 'n { - fn provide <'source> (state: &State, words: &mut TokenIter<'source>) - -> Perhaps; +/// [Give]s instances of [Self] given [TokenIter]. +pub trait Take<'n, State>: Sized + 'n { + fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; + /// Return custom error on [None]. fn provide_or_fail <'source, E: Into>, F: Fn()->E> ( state: &State, words: &mut TokenIter<'source>, error: F ) -> Usually { - if let Some(value) = Provide::::provide(state, words)? { + if let Some(value) = Take::::provide(state, words)? { Ok(value) } else { Result::Err(format!("provide: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) @@ -27,25 +33,25 @@ pub trait Provide<'n, State>: Sized + 'n { } } -/// Implement the [Receive] trait, which boils down to +/// Implement the [Give] trait, which boils down to /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'n, State, A: Provide<'n, State>> Provide<'n, State> for $T { + impl<'n, State, A: Take<'n, State>> Take<'n, State> for $T { fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'n, State, A: Provide<'n, State>, B: Provide<'n, State>> Provide<'n, State> for $T { + impl<'n, State, A: Take<'n, State>, B: Take<'n, State>> Take<'n, State> for $T { fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl<'n> Provide<'n, $S> for $T { + impl<'n> Take<'n, $S> for $T { fn provide <'source> ($state: &$S, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } @@ -55,7 +61,7 @@ pub trait Provide<'n, State>: Sized + 'n { // auto impl graveyard: -//impl<'n, State: Receive, Type: 'n> Provide<'n, State> for Type { +//impl<'n, State: Give, Type: 'n> Take<'n, State> for Type { //fn provide <'source> (state: &State, words: &mut TokenIter<'source>) //-> Perhaps //{ @@ -63,7 +69,7 @@ pub trait Provide<'n, State>: Sized + 'n { //} //} -//impl<'n, Type: Provide<'n, State>, State> Receive for State { +//impl<'n, Type: Take<'n, State>, State> Give for State { //fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { //Type::provide(self, words) //} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 5df10cb..a01e6d6 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,13 +5,13 @@ use std::marker::PhantomData; pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } /// A pre-configured mapping of input events to commands. -pub trait KeyMap<'k, S, C: Provide<'k, S> + Command, I: DslInput> { +pub trait KeyMap<'k, S, C: Take<'k, S> + Command, I: DslInput> { /// Try to find a command that matches the current input event. fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; } /// A [SourceIter] can be a [KeyMap]. -impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for SourceIter<'k> { +impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for SourceIter<'k> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some((token, rest)) = iter.next() { @@ -22,7 +22,7 @@ impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Provide::provide(state, &mut exp_iter)? { + if let Some(command) = Take::provide(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -38,7 +38,7 @@ impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for } /// A [TokenIter] can be a [KeyMap]. -impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for TokenIter<'k> { +impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for TokenIter<'k> { fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { let mut iter = self.clone(); while let Some(next) = iter.next() { @@ -48,7 +48,7 @@ impl<'k, S, C: Provide<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Provide::provide(state, &mut e)? { + if let Some(command) = Take::provide(state, &mut e)? { return Ok(Some(command)) } } @@ -69,7 +69,7 @@ pub type InputCondition<'k, S> = BoxUsually + Send + Sync + ' /// which may be made available subject to given conditions. pub struct InputMap<'k, S, - C: Command + Provide<'k, S>, + C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I> > { @@ -79,7 +79,7 @@ pub struct InputMap<'k, impl<'k, S, - C: Command + Provide<'k, S>, + C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I> > Default for InputMap<'k, S, C, I, M>{ @@ -88,7 +88,7 @@ impl<'k, } } -impl<'k, S, C: Command + Provide<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> +impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> InputMap<'k, S, C, I, M> { pub fn new (keymap: M) -> Self { Self::default().layer(keymap) @@ -111,7 +111,7 @@ InputMap<'k, S, C, I, M> { } } -impl<'k, S, C: Command + Provide<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> +impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> std::fmt::Debug for InputMap<'k, S, C, I, M> { fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "[InputMap: {} layer(s)]", self.layers.len()) @@ -121,7 +121,7 @@ std::fmt::Debug for InputMap<'k, S, C, I, M> { /// An [InputMap] can be a [KeyMap]. impl<'k, S, - C: Command + Provide<'k, S>, + C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I> > KeyMap<'k, S, C, I> for InputMap<'k, S, C, I, M> { diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index ee168ea..4c81616 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -36,7 +36,7 @@ pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); #[cfg(feature = "dsl")] -impl<'n, State: Receive, A: 'n> Provide<'n, State> for Align { +impl<'n, State: Give, A: 'n> Take<'n, State> for Align { fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(key), .. }) = words.peek() { match key { @@ -44,7 +44,7 @@ impl<'n, State: Receive, A: 'n> Provide<'n, State> for Align { "align/n"|"align/s"|"align/e"|"align/w"| "align/nw"|"align/sw"|"align/ne"|"align/se" => { let _ = words.next().unwrap(); - let content = state.receive_or_fail(&mut words.clone(), ||"expected content")?; + let content = state.give_or_fail(&mut words.clone(), ||"expected content")?; return Ok(Some(match key { "align/c" => Self::c(content), "align/x" => Self::x(content), diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index cb38197..d423176 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -21,7 +21,7 @@ impl, B: Content> Content for Bsp { } } #[cfg(feature = "dsl")] -impl<'n, State: Receive + Receive, A: 'n, B: 'n> Provide<'n, State> for Bsp { +impl<'n, State: Give + Give, A: 'n, B: 'n> Take<'n, State> for Bsp { fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), @@ -29,8 +29,8 @@ impl<'n, State: Receive + Receive, A: 'n, B: 'n> Provide<'n, State> for Bs }) = words.peek() { if let Value::Key(key) = words.next().unwrap().value() { let base = words.clone(); - let a: A = state.receive_or_fail(words, ||"bsp: expected content 1")?; - let b: B = state.receive_or_fail(words, ||"bsp: expected content 2")?; + let a: A = state.give_or_fail(words, ||"bsp: expected content 1")?; + let b: B = state.give_or_fail(words, ||"bsp: expected content 2")?; return Ok(Some(match key { "bsp/n" => Self::n(a, b), "bsp/s" => Self::s(a, b), diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs index d3813c1..e234d99 100644 --- a/output/src/ops/either.rs +++ b/output/src/ops/either.rs @@ -9,15 +9,15 @@ impl Either { } } #[cfg(feature = "dsl")] -impl<'n, State: Receive + Receive + Receive, A: 'n, B: 'n> Provide<'n, State> for Either { +impl<'n, State: Give + Give + Give, A: 'n, B: 'n> Take<'n, State> for Either { fn provide <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { let base = token.clone(); let _ = token.next().unwrap(); return Ok(Some(Self( - state.receive_or_fail(token, ||"either: no condition")?, - state.receive_or_fail(token, ||"either: no content 1")?, - state.receive_or_fail(token, ||"either: no content 2")?, + state.give_or_fail(token, ||"either: no condition")?, + state.give_or_fail(token, ||"either: no content 1")?, + state.give_or_fail(token, ||"either: no content 2")?, ))) } Ok(None) diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 2715bde..71b0f0a 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -33,7 +33,7 @@ macro_rules! transform_xy { #[inline] pub const fn xy (item: A) -> Self { Self::XY(item) } } #[cfg(feature = "dsl")] - impl<'n, A: 'n, T: Receive> Provide<'n, T> for $Enum { + impl<'n, A: 'n, T: Give> Take<'n, T> for $Enum { fn provide <'source> (state: &T, token: &mut TokenIter<'source>) -> Perhaps { @@ -41,11 +41,11 @@ macro_rules! transform_xy { let mut base = token.clone(); return Ok(Some(match token.next() { Some(Token{value:Value::Key($x),..}) => - Self::x(state.receive_or_fail(token, ||"x: no content")?), + Self::x(state.give_or_fail(token, ||"x: no content")?), Some(Token{value:Value::Key($y),..}) => - Self::y(state.receive_or_fail(token, ||"y: no content")?), + Self::y(state.give_or_fail(token, ||"y: no content")?), Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.receive_or_fail(token, ||"xy: no content")?), + Self::xy(state.give_or_fail(token, ||"xy: no content")?), _ => unreachable!() })) } @@ -79,7 +79,7 @@ macro_rules! transform_xy_unit { #[inline] pub const fn xy (x: U, y: U, item: A) -> Self { Self::XY(x, y, item) } } #[cfg(feature = "dsl")] - impl<'n, A: 'n, U: Coordinate + 'n, T: Receive + Receive> Provide<'n, T> for $Enum { + impl<'n, A: 'n, U: Coordinate + 'n, T: Give + Give> Take<'n, T> for $Enum { fn provide <'source> ( state: &T, token: &mut TokenIter<'source> ) -> Perhaps { @@ -87,17 +87,17 @@ macro_rules! transform_xy_unit { let mut base = token.clone(); Some(match token.next() { Some(Token { value: Value::Key($x), .. }) => Self::x( - state.receive_or_fail(token, ||"x: no unit")?, - state.receive_or_fail(token, ||"x: no content")?, + state.give_or_fail(token, ||"x: no unit")?, + state.give_or_fail(token, ||"x: no content")?, ), Some(Token { value: Value::Key($y), .. }) => Self::y( - state.receive_or_fail(token, ||"y: no unit")?, - state.receive_or_fail(token, ||"y: no content")?, + state.give_or_fail(token, ||"y: no unit")?, + state.give_or_fail(token, ||"y: no content")?, ), Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.receive_or_fail(token, ||"xy: no unit x")?, - state.receive_or_fail(token, ||"xy: no unit y")?, - state.receive_or_fail(token, ||"xy: no content")? + state.give_or_fail(token, ||"xy: no unit x")?, + state.give_or_fail(token, ||"xy: no unit y")?, + state.give_or_fail(token, ||"xy: no content")? ), _ => unreachable!(), }) diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs index c2c0b1a..2ffde7b 100644 --- a/output/src/ops/when.rs +++ b/output/src/ops/when.rs @@ -2,14 +2,16 @@ use crate::*; /// Show an item only when a condition is true. pub struct When(pub bool, pub A); + impl When { /// Create a binary condition. pub const fn new (c: bool, a: A) -> Self { Self(c, a) } } + #[cfg(feature = "dsl")] -impl<'n, State: Receive + Receive, A: 'n> Provide<'n, State> for When { +impl<'n, State: Give, A: Take<'n, State>> Take<'n, State> for When { fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("when"), @@ -18,14 +20,15 @@ impl<'n, State: Receive + Receive, A: 'n> Provide<'n, State> for When> Content for When { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, item) = self; diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index e16146d..5b8b597 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: Provide::provide_or_fail(self, words)?, + #arg: Take::provide_or_fail(self, words)?, }); } out @@ -149,7 +149,7 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'n> ::tengri::dsl::Provide<'n, #state> for #command_enum { + impl<'n> ::tengri::dsl::Take<'n, #state> for #command_enum { fn provide <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 6911a40..1aff847 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,7 +85,7 @@ impl ToTokens for ExposeImpl { let values = variants.iter().map(ExposeArm::from); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl<'n> ::tengri::dsl::Provide<'n, #state> for #t { + impl<'n> ::tengri::dsl::Take<'n, #state> for #t { fn provide <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index a8d8f7a..39702fe 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -46,7 +46,7 @@ impl ToTokens for ViewDef { let builtins: Vec<_> = builtins_with_types() .iter() .map(|ty|write_quote(quote! { - let value: Option<#ty> = Receive::<#ty>::receive(self, &mut exp.clone())?; + let value: Option<#ty> = Give::<#ty>::give(self, &mut exp.clone())?; if let Some(value) = value { return Ok(Some(value.boxed())) } @@ -65,8 +65,8 @@ impl ToTokens for ViewDef { /// Gives [#self_ty] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl ::tengri::dsl::Receive>> for #self_ty { - fn receive <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) + impl ::tengri::dsl::Give>> for #self_ty { + fn give <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) -> Perhaps>> { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { From cbd28a5934a7a2a9f7c48faa92bfe7ba52440919 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 23 May 2025 21:47:18 +0300 Subject: [PATCH 095/178] dsl: auto-impl the obvious one (hopefully) re foreign trait constraints --- dsl/src/dsl_provide.rs | 24 +++++++++++++++--------- input/src/input_dsl.rs | 4 ++-- output/src/ops/align.rs | 2 +- output/src/ops/bsp.rs | 2 +- output/src/ops/either.rs | 2 +- output/src/ops/transform.rs | 6 +++--- output/src/ops/when.rs | 4 ++-- proc/src/proc_command.rs | 4 ++-- proc/src/proc_expose.rs | 2 +- proc/src/proc_view.rs | 4 ++-- 10 files changed, 30 insertions(+), 24 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index bda4381..a0130f7 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -18,17 +18,23 @@ pub trait Give { } } +impl<'n, T: Take<'n, U>, U> Give for U { + fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { + T::take(self, words) + } +} + /// [Give]s instances of [Self] given [TokenIter]. pub trait Take<'n, State>: Sized + 'n { - fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; /// Return custom error on [None]. - fn provide_or_fail <'source, E: Into>, F: Fn()->E> ( + fn take_or_fail <'source, E: Into>, F: Fn()->E> ( state: &State, words: &mut TokenIter<'source>, error: F ) -> Usually { - if let Some(value) = Take::::provide(state, words)? { + if let Some(value) = Take::::take(state, words)? { Ok(value) } else { - Result::Err(format!("provide: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("take: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) } } } @@ -38,21 +44,21 @@ pub trait Take<'n, State>: Sized + 'n { #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { impl<'n, State, A: Take<'n, State>> Take<'n, State> for $T { - fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { impl<'n, State, A: Take<'n, State>, B: Take<'n, State>> Take<'n, State> for $T { - fn provide <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { impl<'n> Take<'n, $S> for $T { - fn provide <'source> ($state: &$S, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + fn take <'source> ($state: &$S, $words: &mut TokenIter<'source>) -> Perhaps<$T> { $expr } } @@ -62,7 +68,7 @@ pub trait Take<'n, State>: Sized + 'n { // auto impl graveyard: //impl<'n, State: Give, Type: 'n> Take<'n, State> for Type { - //fn provide <'source> (state: &State, words: &mut TokenIter<'source>) + //fn take <'source> (state: &State, words: &mut TokenIter<'source>) //-> Perhaps //{ //state.take(words) @@ -71,6 +77,6 @@ pub trait Take<'n, State>: Sized + 'n { //impl<'n, Type: Take<'n, State>, State> Give for State { //fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { - //Type::provide(self, words) + //Type::take(self, words) //} //} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index a01e6d6..79334f4 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -22,7 +22,7 @@ impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for So match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Take::provide(state, &mut exp_iter)? { + if let Some(command) = Take::take(state, &mut exp_iter)? { return Ok(Some(command)) } } @@ -48,7 +48,7 @@ impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for To match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Take::provide(state, &mut e)? { + if let Some(command) = Take::take(state, &mut e)? { return Ok(Some(command)) } } diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 4c81616..8031e0e 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -37,7 +37,7 @@ pub struct Align(Alignment, A); #[cfg(feature = "dsl")] impl<'n, State: Give, A: 'n> Take<'n, State> for Align { - fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(key), .. }) = words.peek() { match key { "align/c"|"align/x"|"align/y"| diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs index d423176..9ecd6d7 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -22,7 +22,7 @@ impl, B: Content> Content for Bsp { } #[cfg(feature = "dsl")] impl<'n, State: Give + Give, A: 'n, B: 'n> Take<'n, State> for Bsp { - fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), .. diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs index e234d99..a35d555 100644 --- a/output/src/ops/either.rs +++ b/output/src/ops/either.rs @@ -10,7 +10,7 @@ impl Either { } #[cfg(feature = "dsl")] impl<'n, State: Give + Give + Give, A: 'n, B: 'n> Take<'n, State> for Either { - fn provide <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps { + fn take <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { let base = token.clone(); let _ = token.next().unwrap(); diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 71b0f0a..5e0ec04 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -1,7 +1,7 @@ //! [Content] items that modify the inherent //! dimensions of their inner [Render]ables. //! -//! Transform may also react to the [Area] provided. +//! Transform may also react to the [Area] taked. //! ``` //! use ::tengri::{output::*, tui::*}; //! let area: [u16;4] = [10, 10, 20, 20]; @@ -34,7 +34,7 @@ macro_rules! transform_xy { } #[cfg(feature = "dsl")] impl<'n, A: 'n, T: Give> Take<'n, T> for $Enum { - fn provide <'source> (state: &T, token: &mut TokenIter<'source>) + fn take <'source> (state: &T, token: &mut TokenIter<'source>) -> Perhaps { if let Some(Token { value: Value::Key(k), .. }) = token.peek() { @@ -80,7 +80,7 @@ macro_rules! transform_xy_unit { } #[cfg(feature = "dsl")] impl<'n, A: 'n, U: Coordinate + 'n, T: Give + Give> Take<'n, T> for $Enum { - fn provide <'source> ( + fn take <'source> ( state: &T, token: &mut TokenIter<'source> ) -> Perhaps { Ok(if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = token.peek() { diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs index 2ffde7b..db295ab 100644 --- a/output/src/ops/when.rs +++ b/output/src/ops/when.rs @@ -12,7 +12,7 @@ impl When { #[cfg(feature = "dsl")] impl<'n, State: Give, A: Take<'n, State>> Take<'n, State> for When { - fn provide <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { Ok(if let Some(Token { value: Value::Key("when"), .. @@ -21,7 +21,7 @@ impl<'n, State: Give, A: Take<'n, State>> Take<'n, State> for When { let base = words.clone(); return Ok(Some(Self( state.give_or_fail(words, ||"cond: no condition")?, - A::provide_or_fail(state, words, ||"cond: no content")?, + A::take_or_fail(state, words, ||"cond: no content")?, ))) } else { None diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 5b8b597..b8cf812 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: Take::provide_or_fail(self, words)?, + #arg: Take::take_or_fail(self, words)?, }); } out @@ -150,7 +150,7 @@ impl ToTokens for CommandDef { } /// Generated by [tengri_proc::command]. impl<'n> ::tengri::dsl::Take<'n, #state> for #command_enum { - fn provide <'source> ( + fn take <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 1aff847..b0fae39 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -86,7 +86,7 @@ impl ToTokens for ExposeImpl { write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. impl<'n> ::tengri::dsl::Take<'n, #state> for #t { - fn provide <'source> ( + fn take <'source> ( state: &#state, words: &mut ::tengri::dsl::TokenIter<'source> ) -> Perhaps { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 39702fe..f68adc0 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -77,7 +77,7 @@ impl ToTokens for ViewDef { #(#builtins)* None }, - // Symbols are handled by user-provided functions + // Symbols are handled by user-taked functions // that take no parameters but `&self`. ::tengri::dsl::Value::Sym(sym) => match sym { #(#exposed)* @@ -100,7 +100,7 @@ impl ToTokens for ViewDef { #self_ty::view(self) } } - // Original user-provided implementation: + // Original user-taked implementation: #block }) } From 5a2177cc77fd8827b565a2bf08c18d6bc25ea0dd Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 24 May 2025 00:29:50 +0300 Subject: [PATCH 096/178] dsl: give! and take! macros --- dsl/src/dsl_provide.rs | 69 ++++++++++++++++++++++++++++---- output/src/ops/align.rs | 57 ++++++++++++--------------- proc/src/proc_expose.rs | 12 +----- proc/src/proc_view.rs | 87 ++++++++++++++++++++--------------------- 4 files changed, 131 insertions(+), 94 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index a0130f7..85e622d 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -17,13 +17,6 @@ pub trait Give { } } } - -impl<'n, T: Take<'n, U>, U> Give for U { - fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { - T::take(self, words) - } -} - /// [Give]s instances of [Self] given [TokenIter]. pub trait Take<'n, State>: Sized + 'n { fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; @@ -39,6 +32,68 @@ pub trait Take<'n, State>: Sized + 'n { } } +#[macro_export] macro_rules! take { + () => { + impl<'n, Type: 'n, State: Give + 'n> Take<'n, State> for Type { + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + (box) => { + impl<'n, T, State: Give> + 'n> Take<'n, State> for Box { + fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + ($Type:ty:$State:ty) => { + impl<'n> Take<'n, $State> for $Type { + fn take <'source> (state: &$State, words: &mut TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + ($Type:path$(,$Arg:ident)*|$state:ident:$State:path,$words:ident|$expr:expr) => { + impl<'n, State: $State + 'n $(, $Arg: 'n)*> Take<'n, State> for $Type { + fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps { + $expr + } + } + }; +} +#[macro_export] macro_rules! give { + () => { + impl<'n, Type: Take<'n, State>, State> Give for State { + fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { + Type::take(self, words) + } + } + }; + (box) => { + //impl<'n, T, Type: Take<'n, Box>> Give> for Box { + //fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps> { + //Type::take(self, words) + //} + //} + }; + ($Type:ty: $State:ty) => { + impl<'n, $Type: Take<'n, $State>> Give<$Type> for $State { + fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps<$Type> { + $Type::take(self, words) + } + } + }; + ($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { + impl Give<$Type> for $State { + fn give <'source> (&self, $words: &mut TokenIter<'source>) -> Perhaps<$Type> { + let $state = self; + $expr + } + } + }; +} + /// Implement the [Give] trait, which boils down to /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 8031e0e..1626da5 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -27,45 +27,36 @@ //! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); //! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); //! ``` - use crate::*; - #[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } - pub struct Align(Alignment, A); - -#[cfg(feature = "dsl")] -impl<'n, State: Give, A: 'n> Take<'n, State> for Align { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { - if let Some(Token { value: Value::Key(key), .. }) = words.peek() { +#[cfg(feature = "dsl")]take!(Align, A|state: Give, words|Ok(Some(match words.peek() { + Some(Token { value: Value::Key(key), .. }) => match key { + "align/c"|"align/x"|"align/y"| + "align/n"|"align/s"|"align/e"|"al;qign/w"| + "align/nw"|"align/sw"|"align/ne"|"align/se" => { + let _ = words.next().unwrap(); + let content = state.give_or_fail(&mut words.clone(), ||"expected content")?; 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 _ = words.next().unwrap(); - let content = state.give_or_fail(&mut words.clone(), ||"expected content")?; - 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!() - })) - }, - _ => {} + "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!() } - } - Ok(None) - } -} + }, + _ => return Ok(None) + }, + _ => return Ok(None) +}))); impl Align { #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index b0fae39..11f4c03 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -121,11 +121,7 @@ impl ToTokens for ExposeArm { } } -impl From for ExposeSym { - fn from (this: LitStr) -> Self { - Self(this) - } -} +impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) } } impl PartialOrd for ExposeSym { fn partial_cmp (&self, other: &Self) -> Option { @@ -153,11 +149,7 @@ impl PartialEq for ExposeSym { impl Eq for ExposeSym {} -impl From for ExposeType { - fn from (this: Type) -> Self { - Self(Box::new(this)) - } -} +impl From for ExposeType { fn from (this: Type) -> Self { Self(Box::new(this)) } } impl PartialOrd for ExposeType { fn partial_cmp (&self, other: &Self) -> Option { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index f68adc0..08d1684 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -4,9 +4,7 @@ use crate::*; pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); #[derive(Debug, Clone)] -pub(crate) struct ViewMeta { - pub(crate) output: Ident, -} +pub(crate) struct ViewMeta { pub(crate) output: Ident } #[derive(Debug, Clone)] pub(crate) struct ViewImpl { @@ -43,53 +41,54 @@ impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let self_ty = &block.self_ty; - let builtins: Vec<_> = builtins_with_types() - .iter() - .map(|ty|write_quote(quote! { - let value: Option<#ty> = Give::<#ty>::give(self, &mut exp.clone())?; - if let Some(value) = value { - return Ok(Some(value.boxed())) - } - })) - .collect(); - let exposed: Vec<_> = exposed - .iter() - .map(|(key, value)|write_quote(quote! { - #key => { - Some(Box::new(Thunk::new(move||#self_ty::#value(self))))//Box::new("todo")) - }, - })).collect(); + // Expressions are handled by built-in functions + // that operate over constants and symbols. + let builtins: Vec<_> = builtins_with_types().iter().map(|ty|write_quote(quote! { + ::tengri::dsl::Value::Exp(_, expr) => { + Give::<#ty>::give(&mut expr.clone()).map(|value|value.boxed()) + }, + })).collect(); + // Symbols are handled by user-taked functions + // that take no parameters but `&self`. + let exposed: Vec<_> = exposed.iter().map(|(key, value)|write_quote(quote! { + ::tengri::dsl::Value::Sym(#key) => { + Some(Box::new(Thunk::new(move||#self_ty::#value(self)))) + }, + })).collect(); write_quote_to(out, quote! { /// Generated by [tengri_proc]. /// /// Gives [#self_ty] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - impl ::tengri::dsl::Give>> for #self_ty { - fn give <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) - -> Perhaps>> - { - Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { - match value { - // Expressions are handled by built-in functions - // that operate over constants and symbols. - ::tengri::dsl::Value::Exp(_, exp) => { - #(#builtins)* - None - }, - // Symbols are handled by user-taked functions - // that take no parameters but `&self`. - ::tengri::dsl::Value::Sym(sym) => match sym { - #(#exposed)* - _ => None - }, - _ => None - } - } else { - None - }) - } - } + give!(Box>|state:#self_ty, words|Ok(None)); + //impl <'n, State: ::tengri::dsl::Give>>> + //Take<'n, Box> for #self_ty + //{ + //fn give <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) + //-> Perhaps>> + //{ + //Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { + //match value { + //// Expressions are handled by built-in functions + //// that operate over constants and symbols. + //::tengri::dsl::Value::Exp(_, exp) => { + //#(#builtins)* + //None + //}, + //// Symbols are handled by user-taked functions + //// that take no parameters but `&self`. + //::tengri::dsl::Value::Sym(sym) => match sym { + //#(#exposed)* + //_ => None + //}, + //_ => None + //} + //} else { + //None + //}) + //} + //} /// Generated by [tengri_proc]. /// /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, From 3e1084555ba3a12bf508db4211168a578ab23ffd Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 24 May 2025 23:57:12 +0300 Subject: [PATCH 097/178] wip: dsl, output, input, proc, tui: sorting out give and take --- dsl/src/dsl_provide.rs | 163 +++++++++++++++++++++--------------- input/src/input_dsl.rs | 4 +- output/src/ops/align.rs | 58 +++++++------ output/src/ops/bsp.rs | 49 +++++------ output/src/ops/either.rs | 27 +++--- output/src/ops/transform.rs | 104 ++++++++++------------- output/src/ops/when.rs | 31 +++---- proc/src/proc_command.rs | 7 +- proc/src/proc_expose.rs | 37 ++++---- proc/src/proc_view.rs | 94 +++++++++------------ 10 files changed, 273 insertions(+), 301 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index 85e622d..d63426e 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -3,117 +3,144 @@ use crate::*; // maybe their names should be switched around? /// [Take]s instances of [Type] given [TokenIter]. -pub trait Give { +pub trait Give<'state, Type> { /// Implement this to be able to [Give] [Type] from the [TokenIter]. - fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps; + fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps; /// Return custom error on [None]. - fn give_or_fail <'source, E: Into>, F: Fn()->E> ( - &self, words: &mut TokenIter<'source>, error: F + fn give_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( + &self, mut words: TokenIter<'source>, error: F ) -> Usually { + let next = words.peek().map(|x|x.value).clone(); if let Some(value) = Give::::give(self, words)? { Ok(value) } else { - Result::Err(format!("give: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) + Result::Err(format!("give: {}: {next:?}", error().into()).into()) } } } -/// [Give]s instances of [Self] given [TokenIter]. -pub trait Take<'n, State>: Sized + 'n { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps; - /// Return custom error on [None]. - fn take_or_fail <'source, E: Into>, F: Fn()->E> ( - state: &State, words: &mut TokenIter<'source>, error: F - ) -> Usually { - if let Some(value) = Take::::take(state, words)? { - Ok(value) - } else { - Result::Err(format!("take: {}: {:?}", error().into(), words.peek().map(|x|x.value)).into()) - } - } -} - -#[macro_export] macro_rules! take { - () => { - impl<'n, Type: 'n, State: Give + 'n> Take<'n, State> for Type { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - (box) => { - impl<'n, T, State: Give> + 'n> Take<'n, State> for Box { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:ty:$State:ty) => { - impl<'n> Take<'n, $State> for $Type { - fn take <'source> (state: &$State, words: &mut TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident:$State:path,$words:ident|$expr:expr) => { - impl<'n, State: $State + 'n $(, $Arg: 'n)*> Take<'n, State> for $Type { - fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps { - $expr - } - } - }; -} #[macro_export] macro_rules! give { () => { - impl<'n, Type: Take<'n, State>, State> Give for State { - fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { + impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { + fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps { Type::take(self, words) } } }; (box) => { - //impl<'n, T, Type: Take<'n, Box>> Give> for Box { - //fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps> { + //impl<'state, T, Type: Take<'state, Box>> Give<'state, Box> for Box { + //fn give (&self, mut words:TokenIter<'source>) -> Perhaps> { //Type::take(self, words) //} //} }; ($Type:ty: $State:ty) => { - impl<'n, $Type: Take<'n, $State>> Give<$Type> for $State { - fn give <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps<$Type> { + impl<'state, $Type: Take<'state, $State>> Give<'state, $Type> for $State { + fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps<$Type> { $Type::take(self, words) } } }; ($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { - impl Give<$Type> for $State { - fn give <'source> (&self, $words: &mut TokenIter<'source>) -> Perhaps<$Type> { + impl<'state> Give<'state, $Type> for $State { + fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { let $state = self; $expr } } }; + ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { + impl<'state, State: $(Give<'state, $Arg> +)* 'state $(, $Arg)*> Give<'state, $Type> for State { + fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { + let $state = self; + $expr + } + } + } +} +/// [Give]s instances of [Self] given [TokenIter]. +pub trait Take<'state, State>: Sized { + fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps; + /// Return custom error on [None]. + fn take_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( + state: &State, mut words:TokenIter<'source>, error: F + ) -> Usually { + let next = words.peek().map(|x|x.value).clone(); + if let Some(value) = Take::::take(state, words)? { + Ok(value) + } else { + Result::Err(format!("take: {}: {next:?}", error().into()).into()) + } + } +} +#[macro_export] macro_rules! take { + () => { + impl<'state, Type: 'state, State: Give<'state, Type> + 'state> Take<'state, State> for Type { + fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + (box) => { + impl<'state, T, State: Give<'state, Box> + 'state> Take<'state, State> for Box { + fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + ($Type:ty:$State:ty) => { + impl<'state> Take<'state, $State> for $Type { + fn take <'source: 'state> (state: &$State, mut words:TokenIter<'source>) -> Perhaps { + state.give(words) + } + } + }; + ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { + impl<'state, + State: Give<'state, bool> + $(Give<'state, $Arg> + )* 'state + $(, $Arg: Take<'state, State> + 'state)* + > Take<'state, State> for $Type { + fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps { + $expr + } + } + }; + ($Type:path$(,$Arg:ident)*|$state:ident:$State:path,$words:ident|$expr:expr) => { + impl<'state $(, $Arg: 'state)*> Take<'state, $State> for $Type { + fn take <'source: 'state> ($state: &$State, mut $words:TokenIter<'source>) -> Perhaps { + $expr + } + } + }; +} + +#[cfg(feature="dsl")] +impl<'state, E: 'state, State: Give<'state, Box + 'state>>> +Take<'state, State> for Box + 'state> { + fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { + state.give(words) + } } /// Implement the [Give] trait, which boils down to /// specifying two types and providing an expression. #[macro_export] macro_rules! from_dsl { (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'n, State, A: Take<'n, State>> Take<'n, State> for $T { - fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + impl<'state, State, A: Take<'state, State>> Take<'state, State> for $T { + fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps<$T> { $expr } } }; (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'n, State, A: Take<'n, State>, B: Take<'n, State>> Take<'n, State> for $T { - fn take <'source> ($state: &State, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + impl<'state, State, A: Take<'state, State>, B: Take<'state, State>> Take<'state, State> for $T { + fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps<$T> { $expr } } }; ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl<'n> Take<'n, $S> for $T { - fn take <'source> ($state: &$S, $words: &mut TokenIter<'source>) -> Perhaps<$T> { + impl<'state> Take<'state, $S> for $T { + fn take <'source: 'state> ($state: &$S, mut $words:TokenIter<'source>) -> Perhaps<$T> { $expr } } @@ -122,16 +149,16 @@ pub trait Take<'n, State>: Sized + 'n { // auto impl graveyard: -//impl<'n, State: Give, Type: 'n> Take<'n, State> for Type { - //fn take <'source> (state: &State, words: &mut TokenIter<'source>) +//impl<'state, State: Give, Type: 'state> Take<'state, State> for Type { + //fn take <'state> (state: &State, mut words:TokenIter<'source>) //-> Perhaps //{ //state.take(words) //} //} -//impl<'n, Type: Take<'n, State>, State> Give for State { - //fn take <'source> (&self, words: &mut TokenIter<'source>) -> Perhaps { +//impl<'state, Type: Take<'state, State>, State> Give for State { + //fn take <'state> (&self, mut words:TokenIter<'source>) -> Perhaps { //Type::take(self, words) //} //} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 79334f4..9c08545 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -22,7 +22,7 @@ impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for So match exp_iter.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, &mut exp_iter)? { + if let Some(command) = Take::take(state, exp_iter)? { return Ok(Some(command)) } } @@ -48,7 +48,7 @@ impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for To match e.next() { Some(Token { value: Value::Sym(binding), .. }) => { if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, &mut e)? { + if let Some(command) = Take::take(state, e)? { return Ok(Some(command)) } } diff --git a/output/src/ops/align.rs b/output/src/ops/align.rs index 1626da5..ffd5b48 100644 --- a/output/src/ops/align.rs +++ b/output/src/ops/align.rs @@ -31,32 +31,38 @@ use crate::*; #[derive(Debug, Copy, Clone, Default)] pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub struct Align(Alignment, A); -#[cfg(feature = "dsl")]take!(Align, A|state: Give, words|Ok(Some(match words.peek() { - Some(Token { value: Value::Key(key), .. }) => match key { - "align/c"|"align/x"|"align/y"| - "align/n"|"align/s"|"align/e"|"al;qign/w"| - "align/nw"|"align/sw"|"align/ne"|"align/se" => { - let _ = words.next().unwrap(); - let content = state.give_or_fail(&mut words.clone(), ||"expected content")?; - 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) - }, - _ => return Ok(None) -}))); +#[cfg(feature = "dsl")] +impl<'state, State: Give<'state, A>, A: 'state> Take<'state, State> for Align { + fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { + todo!() + } +} +//give!(Align, A|state, words|Ok(Some(match words.peek() { + //Some(Token { value: Value::Key(key), .. }) => match key { + //"align/c"|"align/x"|"align/y"| + //"align/n"|"align/s"|"align/e"|"al;qign/w"| + //"align/nw"|"align/sw"|"align/ne"|"align/se" => { + //let _ = words.next().unwrap(); + //let content = Take::take_or_fail(state, &mut words.clone(), ||"expected content")?; + //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) + //}, + //_ => return Ok(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 9ecd6d7..e714f8e 100644 --- a/output/src/ops/bsp.rs +++ b/output/src/ops/bsp.rs @@ -20,34 +20,29 @@ impl, B: Content> Content for Bsp { } } } -#[cfg(feature = "dsl")] -impl<'n, State: Give + Give, A: 'n, B: 'n> Take<'n, State> for Bsp { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { - Ok(if let Some(Token { - value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), - .. - }) = words.peek() { - if let Value::Key(key) = words.next().unwrap().value() { - let base = words.clone(); - let a: A = state.give_or_fail(words, ||"bsp: expected content 1")?; - let b: B = state.give_or_fail(words, ||"bsp: expected content 2")?; - return Ok(Some(match key { - "bsp/n" => Self::n(a, b), - "bsp/s" => Self::s(a, b), - "bsp/e" => Self::e(a, b), - "bsp/w" => Self::w(a, b), - "bsp/a" => Self::a(a, b), - "bsp/b" => Self::b(a, b), - _ => unreachable!(), - })) - } else { - unreachable!() - } - } else { - None - }) +#[cfg(feature = "dsl")] take!(Bsp, A, B|state, words|Ok(if let Some(Token { + value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), + .. +}) = words.peek() { + if let Value::Key(key) = words.next().unwrap().value() { + let base = words.clone(); + let a: A = state.give_or_fail(words, ||"bsp: expected content 1")?; + let b: B = state.give_or_fail(words, ||"bsp: expected content 2")?; + return Ok(Some(match key { + "bsp/n" => Self::n(a, b), + "bsp/s" => Self::s(a, b), + "bsp/e" => Self::e(a, b), + "bsp/w" => Self::w(a, b), + "bsp/a" => Self::a(a, b), + "bsp/b" => Self::b(a, b), + _ => unreachable!(), + })) + } else { + unreachable!() } -} +} else { + 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/either.rs b/output/src/ops/either.rs index a35d555..29ab0e8 100644 --- a/output/src/ops/either.rs +++ b/output/src/ops/either.rs @@ -8,21 +8,18 @@ impl Either { Self(c, a, b) } } -#[cfg(feature = "dsl")] -impl<'n, State: Give + Give + Give, A: 'n, B: 'n> Take<'n, State> for Either { - fn take <'source> (state: &State, token: &mut TokenIter<'source>) -> Perhaps { - if let Some(Token { value: Value::Key("either"), .. }) = token.peek() { - let base = token.clone(); - let _ = token.next().unwrap(); - return Ok(Some(Self( - state.give_or_fail(token, ||"either: no condition")?, - state.give_or_fail(token, ||"either: no content 1")?, - state.give_or_fail(token, ||"either: no content 2")?, - ))) - } - Ok(None) - } -} +#[cfg(feature = "dsl")] take!(Either, A, B|state, words|Ok( +if let Some(Token { value: Value::Key("either"), .. }) = words.peek() { + let base = words.clone(); + let _ = words.next().unwrap(); + return Ok(Some(Self( + state.give_or_fail(words, ||"either: no condition")?, + state.give_or_fail(words, ||"either: no content 1")?, + state.give_or_fail(words, ||"either: no content 2")?, + ))) +} else { + None +})); impl, B: Render> Content for Either { fn layout (&self, to: E::Area) -> E::Area { let Self(cond, a, b) = self; diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 5e0ec04..593a518 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -32,26 +32,19 @@ macro_rules! transform_xy { #[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")] - impl<'n, A: 'n, T: Give> Take<'n, T> for $Enum { - fn take <'source> (state: &T, token: &mut TokenIter<'source>) - -> Perhaps - { - if let Some(Token { value: Value::Key(k), .. }) = token.peek() { - let mut base = token.clone(); - return Ok(Some(match token.next() { - Some(Token{value:Value::Key($x),..}) => - Self::x(state.give_or_fail(token, ||"x: no content")?), - Some(Token{value:Value::Key($y),..}) => - Self::y(state.give_or_fail(token, ||"y: no content")?), - Some(Token{value:Value::Key($xy),..}) => - Self::xy(state.give_or_fail(token, ||"xy: no content")?), - _ => unreachable!() - })) - } - Ok(None) - } - } + #[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 { @@ -78,56 +71,45 @@ macro_rules! transform_xy_unit { #[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")] - impl<'n, A: 'n, U: Coordinate + 'n, T: Give + Give> Take<'n, T> for $Enum { - fn take <'source> ( - state: &T, token: &mut TokenIter<'source> - ) -> Perhaps { - Ok(if let Some(Token { value: Value::Key($x|$y|$xy), .. }) = token.peek() { - let mut base = token.clone(); - Some(match token.next() { - Some(Token { value: Value::Key($x), .. }) => Self::x( - state.give_or_fail(token, ||"x: no unit")?, - state.give_or_fail(token, ||"x: no content")?, - ), - Some(Token { value: Value::Key($y), .. }) => Self::y( - state.give_or_fail(token, ||"y: no unit")?, - state.give_or_fail(token, ||"y: no content")?, - ), - Some(Token { value: Value::Key($x), .. }) => Self::xy( - state.give_or_fail(token, ||"xy: no unit x")?, - state.give_or_fail(token, ||"xy: no unit y")?, - state.give_or_fail(token, ||"xy: no content")? - ), - _ => unreachable!(), - }) - } else { - None + #[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 content (&self) -> impl Render + '_ { - Some(match self { - Self::X(_, content) => content, - Self::Y(_, content) => content, - Self::XY(_, _, content) => content, - }) - } 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 { + impl $Enum { #[inline] pub fn dx (&self) -> U { - match self { - Self::X(x, _) => *x, Self::Y(_, _) => 0.into(), Self::XY(x, _, _) => *x, - } + use $Enum::*; + match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, } } #[inline] pub fn dy (&self) -> U { - match self { - Self::X(_, _) => 0.into(), Self::Y(y, _) => *y, Self::XY(_, y, _) => *y, - } + use $Enum::*; + match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, } } } } diff --git a/output/src/ops/when.rs b/output/src/ops/when.rs index db295ab..6324900 100644 --- a/output/src/ops/when.rs +++ b/output/src/ops/when.rs @@ -1,33 +1,22 @@ use crate::*; - /// Show an item only when a condition is true. pub struct When(pub bool, pub A); - impl When { /// Create a binary condition. pub const fn new (c: bool, a: A) -> Self { Self(c, a) } } - -#[cfg(feature = "dsl")] -impl<'n, State: Give, A: Take<'n, State>> Take<'n, State> for When { - fn take <'source> (state: &State, words: &mut TokenIter<'source>) -> Perhaps { - Ok(if let Some(Token { - value: Value::Key("when"), - .. - }) = words.peek() { - let _ = words.next(); - let base = words.clone(); - return Ok(Some(Self( - state.give_or_fail(words, ||"cond: no condition")?, - A::take_or_fail(state, words, ||"cond: no content")?, - ))) - } else { - None - }) - } -} +#[cfg(feature = "dsl")]take!(When, A|state, words|Ok(Some(match words.peek() { + Some(Token { value: Value::Key("when"), .. }) => { + let _ = words.next(); + let base = words.clone(); + let cond = state.give_or_fail(words, ||"cond: no condition")?; + let cont = state.give_or_fail(words, ||"cond: no content")?; + Self(cond, cont) + }, + _ => return Ok(None) +}))); impl> Content for When { fn layout (&self, to: E::Area) -> E::Area { diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index b8cf812..ddbc205 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -149,10 +149,9 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'n> ::tengri::dsl::Take<'n, #state> for #command_enum { - fn take <'source> ( - state: &#state, - words: &mut ::tengri::dsl::TokenIter<'source> + impl<'state> ::tengri::dsl::Take<'state, #state> for #command_enum { + fn take <'source: 'state> ( + state: &#state, mut words: ::tengri::dsl::TokenIter<'source> ) -> Perhaps { let mut words = words.clone(); let token = words.next(); diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 11f4c03..4d0b928 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -9,9 +9,6 @@ pub(crate) struct ExposeMeta; #[derive(Debug, Clone)] pub(crate) struct ExposeImpl(ItemImpl, BTreeMap>); -#[derive(Debug, Clone)] -struct ExposeArm(String, Ident); - #[derive(Debug, Clone)] struct ExposeSym(LitStr); @@ -82,13 +79,23 @@ impl ToTokens for ExposeImpl { }, _ => quote! {}, }; - let values = variants.iter().map(ExposeArm::from); + let values = variants.iter().map(|(key, value)|{ + let key = LitStr::new(&key, Span::call_site()); + quote! { Some(::tengri::dsl::Value::Sym(#key)) => state.#value(), } + }); write_quote_to(out, quote! { + /// Generated by [tengri_proc::expose]. + impl<'n> ::tengri::dsl::Give<'n, #t> for #state { + fn give <'source: 'n> ( + &self, words: ::tengri::dsl::TokenIter<'source> + ) -> Perhaps<#t> { + Take::take(self, words) + } + } /// Generated by [tengri_proc::expose]. impl<'n> ::tengri::dsl::Take<'n, #state> for #t { - fn take <'source> ( - state: &#state, - words: &mut ::tengri::dsl::TokenIter<'source> + fn take <'source: 'n> ( + state: &#state, mut words: ::tengri::dsl::TokenIter<'source> ) -> Perhaps { Ok(Some(match words.next().map(|x|x.value) { #predefined @@ -105,22 +112,6 @@ impl ToTokens for ExposeImpl { } } -impl From<(&String, &Ident)> for ExposeArm { - fn from ((a, b): (&String, &Ident)) -> Self { - Self(a.clone(), b.clone()) - } -} - -impl ToTokens for ExposeArm { - fn to_tokens (&self, out: &mut TokenStream2) { - let Self(key, value) = self; - let key = LitStr::new(&key, Span::call_site()); - write_quote_to(out, quote! { - Some(::tengri::dsl::Value::Sym(#key)) => state.#value(), - }) - } -} - impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) } } impl PartialOrd for ExposeSym { diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 08d1684..f16852f 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -43,52 +43,38 @@ impl ToTokens for ViewDef { let self_ty = &block.self_ty; // Expressions are handled by built-in functions // that operate over constants and symbols. - let builtins: Vec<_> = builtins_with_types().iter().map(|ty|write_quote(quote! { - ::tengri::dsl::Value::Exp(_, expr) => { - Give::<#ty>::give(&mut expr.clone()).map(|value|value.boxed()) - }, - })).collect(); + let builtin = builtins_with_types().map(|ty|write_quote(quote! { #ty })); // Symbols are handled by user-taked functions // that take no parameters but `&self`. - let exposed: Vec<_> = exposed.iter().map(|(key, value)|write_quote(quote! { - ::tengri::dsl::Value::Sym(#key) => { - Some(Box::new(Thunk::new(move||#self_ty::#value(self)))) - }, - })).collect(); + let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { + ::tengri::dsl::Value::Sym(#key) => Some(Box::new(Thunk::new( + move||#self_ty::#value(state))))})); write_quote_to(out, quote! { /// Generated by [tengri_proc]. /// /// Gives [#self_ty] the ability to construct the [Render]able /// which might corresponds to a given [TokenStream], /// while taking [#self_ty]'s state into consideration. - give!(Box>|state:#self_ty, words|Ok(None)); - //impl <'n, State: ::tengri::dsl::Give>>> - //Take<'n, Box> for #self_ty - //{ - //fn give <'source> (&self, words: &mut ::tengri::dsl::TokenIter<'source>) - //-> Perhaps>> - //{ - //Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { - //match value { - //// Expressions are handled by built-in functions - //// that operate over constants and symbols. - //::tengri::dsl::Value::Exp(_, exp) => { - //#(#builtins)* - //None - //}, - //// Symbols are handled by user-taked functions - //// that take no parameters but `&self`. - //::tengri::dsl::Value::Sym(sym) => match sym { - //#(#exposed)* - //_ => None - //}, - //_ => None - //} - //} else { - //None - //}) - //} - //} + impl<'state> Give<'state, Box + 'state>> for #self_ty { + fn give <'source: 'state> (&self, mut words: TokenIter<'source>) + -> Perhaps + 'state>> + { + let state = self; + Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { + match value { + #(::tengri::dsl::Value::Exp(_, expr) => { + #builtin::take(state, expr)?.map(|value|value.boxed()) + },)* + #( + #exposed, + )* + _ => None + } + } else { + None + }) + } + } /// Generated by [tengri_proc]. /// /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, @@ -105,21 +91,21 @@ impl ToTokens for ViewDef { } } -fn builtins_with_types () -> [TokenStream2;14] { +fn builtins_with_types () -> impl Iterator { [ - quote! { When< Box + '_> > }, - quote! { Either< Box + '_>, Box + '_>> }, - quote! { Align< Box + '_> > }, - quote! { Bsp< Box + '_>, Box + '_>> }, - quote! { Fill< Box + '_> > }, - quote! { Fixed<_, Box + '_> > }, - quote! { Min<_, Box + '_> > }, - quote! { Max<_, Box + '_> > }, - quote! { Shrink<_, Box + '_> > }, - quote! { Expand<_, Box + '_> > }, - quote! { Push<_, Box + '_> > }, - quote! { Pull<_, Box + '_> > }, - quote! { Margin<_, Box + '_> > }, - quote! { Padding<_, Box + '_> > }, - ] + quote! { When::< Box> > }, + quote! { Either::< Box>, Box>> }, + quote! { Align::< Box> > }, + quote! { Bsp::< Box>, Box>> }, + quote! { Fill::< Box> > }, + quote! { Fixed::<_, Box> > }, + quote! { Min::<_, Box> > }, + quote! { Max::<_, Box> > }, + quote! { Shrink::<_, Box> > }, + quote! { Expand::<_, Box> > }, + quote! { Push::<_, Box> > }, + quote! { Pull::<_, Box> > }, + quote! { Margin::<_, Box> > }, + quote! { Padding::<_, Box> > }, + ].into_iter() } From 31e84bf5b3a44fb0b51f853f135109ea184ace84 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 25 May 2025 11:43:35 +0300 Subject: [PATCH 098/178] proc: builtims --- dsl/src/dsl_provide.rs | 59 +++++++++++++++-------------------------- proc/src/proc_expose.rs | 8 ------ proc/src/proc_view.rs | 56 +++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 68 deletions(-) diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs index d63426e..34dabd6 100644 --- a/dsl/src/dsl_provide.rs +++ b/dsl/src/dsl_provide.rs @@ -1,10 +1,17 @@ use crate::*; -// maybe their names should be switched around? +///// Implement the [Give] trait, which boils down to +///// specifying two types and providing an expression. +#[macro_export] macro_rules! from_dsl { + ($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { + take! { $Type|$state:$State,$words|$expr } + }; +} /// [Take]s instances of [Type] given [TokenIter]. pub trait Give<'state, Type> { /// Implement this to be able to [Give] [Type] from the [TokenIter]. + /// Advance the stream if returning `Ok>`. fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps; /// Return custom error on [None]. fn give_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( @@ -59,6 +66,8 @@ pub trait Give<'state, Type> { } /// [Give]s instances of [Self] given [TokenIter]. pub trait Take<'state, State>: Sized { + /// Implement this to be able to [Take] [Self] from the [TokenIter]. + /// Advance the stream if returning `Ok>`. fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps; /// Return custom error on [None]. fn take_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( @@ -113,40 +122,6 @@ pub trait Take<'state, State>: Sized { }; } -#[cfg(feature="dsl")] -impl<'state, E: 'state, State: Give<'state, Box + 'state>>> -Take<'state, State> for Box + 'state> { - fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { - state.give(words) - } -} - -/// Implement the [Give] trait, which boils down to -/// specifying two types and providing an expression. -#[macro_export] macro_rules! from_dsl { - (@a: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'state, State, A: Take<'state, State>> Take<'state, State> for $T { - fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps<$T> { - $expr - } - } - }; - (@ab: $T:ty: |$state:ident, $words:ident|$expr:expr) => { - impl<'state, State, A: Take<'state, State>, B: Take<'state, State>> Take<'state, State> for $T { - fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps<$T> { - $expr - } - } - }; - ($T:ty: |$state:ident:$S:ty, $words:ident|$expr:expr) => { - impl<'state> Take<'state, $S> for $T { - fn take <'source: 'state> ($state: &$S, mut $words:TokenIter<'source>) -> Perhaps<$T> { - $expr - } - } - }; -} - // auto impl graveyard: //impl<'state, State: Give, Type: 'state> Take<'state, State> for Type { @@ -157,8 +132,16 @@ Take<'state, State> for Box + 'state> { //} //} -//impl<'state, Type: Take<'state, State>, State> Give for State { - //fn take <'state> (&self, mut words:TokenIter<'source>) -> Perhaps { - //Type::take(self, words) +//#[cfg(feature="dsl")] +//impl<'state, E: 'state, State: Give<'state, Box + 'state>>> +//Take<'state, State> for Box + 'state> { + //fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { + //state.give(words) //} //} + +impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { + fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps { + Type::take(self, words) + } +} diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 4d0b928..f345e43 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -84,14 +84,6 @@ impl ToTokens for ExposeImpl { quote! { Some(::tengri::dsl::Value::Sym(#key)) => state.#value(), } }); write_quote_to(out, quote! { - /// Generated by [tengri_proc::expose]. - impl<'n> ::tengri::dsl::Give<'n, #t> for #state { - fn give <'source: 'n> ( - &self, words: ::tengri::dsl::TokenIter<'source> - ) -> Perhaps<#t> { - Take::take(self, words) - } - } /// Generated by [tengri_proc::expose]. impl<'n> ::tengri::dsl::Take<'n, #state> for #t { fn take <'source: 'n> ( diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index f16852f..578bbbf 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -43,7 +43,7 @@ impl ToTokens for ViewDef { let self_ty = &block.self_ty; // Expressions are handled by built-in functions // that operate over constants and symbols. - let builtin = builtins_with_types().map(|ty|write_quote(quote! { #ty })); + let builtin = builtins_with_boxes_output(quote! { #output }); // Symbols are handled by user-taked functions // that take no parameters but `&self`. let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { @@ -52,18 +52,18 @@ impl ToTokens for ViewDef { write_quote_to(out, quote! { /// Generated by [tengri_proc]. /// - /// Gives [#self_ty] the ability to construct the [Render]able - /// which might corresponds to a given [TokenStream], + /// 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> Give<'state, Box + 'state>> for #self_ty { - fn give <'source: 'state> (&self, mut words: TokenIter<'source>) + impl<'state: 'static> Take<'state, #self_ty> for Box + 'state> { + fn take <'source: 'state> (state: &#self_ty, mut words: TokenIter<'source>) -> Perhaps + 'state>> { - let state = self; + //let state = self; Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { match value { #(::tengri::dsl::Value::Exp(_, expr) => { - #builtin::take(state, expr)?.map(|value|value.boxed()) + Give::<'state, #builtin>::give(state, expr)?.map(|value|value.boxed()) },)* #( #exposed, @@ -91,21 +91,33 @@ impl ToTokens for ViewDef { } } -fn builtins_with_types () -> impl Iterator { +fn builtins_with_holes () -> impl Iterator { + builtins_with(quote! { _ }, quote! { _ }) +} + +fn builtins_with_boxes () -> impl Iterator { + builtins_with(quote! { _ }, quote! { Box+'state> }) +} + +fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator { + builtins_with(quote! { _ }, quote! { Box+'state> }) +} + +fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator { [ - quote! { When::< Box> > }, - quote! { Either::< Box>, Box>> }, - quote! { Align::< Box> > }, - quote! { Bsp::< Box>, Box>> }, - quote! { Fill::< Box> > }, - quote! { Fixed::<_, Box> > }, - quote! { Min::<_, Box> > }, - quote! { Max::<_, Box> > }, - quote! { Shrink::<_, Box> > }, - quote! { Expand::<_, Box> > }, - quote! { Push::<_, Box> > }, - quote! { Pull::<_, Box> > }, - quote! { Margin::<_, Box> > }, - quote! { Padding::<_, Box> > }, + quote! { When::< #c > }, + quote! { Either::< #c, #c> }, + quote! { Align::< #c > }, + quote! { Bsp::< #c, #c> }, + quote! { Fill::< #c > }, + quote! { Fixed::<#n, #c > }, + quote! { Min::<#n, #c > }, + quote! { Max::<#n, #c > }, + quote! { Shrink::<#n, #c > }, + quote! { Expand::<#n, #c > }, + quote! { Push::<#n, #c > }, + quote! { Pull::<#n, #c > }, + quote! { Margin::<#n, #c > }, + quote! { Padding::<#n, #c > }, ].into_iter() } From f1b24d436a890b75cf98aaa0f2d29d10ed660106 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 25 May 2025 22:48:29 +0300 Subject: [PATCH 099/178] wip: ast/cst --- dsl/src/dsl_ast.rs | 70 +++++++ dsl/src/dsl_cst.rs | 291 ++++++++++++++++++++++++++ dsl/src/dsl_domain.rs | 64 ++++++ dsl/src/dsl_parse.rs | 173 --------------- dsl/src/dsl_provide.rs | 147 ------------- dsl/src/dsl_token.rs | 110 ---------- dsl/src/lib.rs | 7 +- input/src/input_dsl.rs | 182 +++++----------- output/src/lib.rs | 16 +- output/src/ops.rs | 382 +++++++++++++++++++++++++++++++++- output/src/ops/align.rs | 110 ---------- output/src/ops/bsp.rs | 122 ----------- output/src/ops/either.rs | 32 --- output/src/ops/transform.rs | 192 ----------------- output/src/ops/when.rs | 38 ---- output/src/ops_dsl.rs | 185 ++++++++++++++++ output/src/space/direction.rs | 1 + proc/src/proc_command.rs | 6 +- proc/src/proc_expose.rs | 6 +- proc/src/proc_view.rs | 50 ++--- 20 files changed, 1081 insertions(+), 1103 deletions(-) create mode 100644 dsl/src/dsl_ast.rs create mode 100644 dsl/src/dsl_cst.rs create mode 100644 dsl/src/dsl_domain.rs delete mode 100644 dsl/src/dsl_parse.rs delete mode 100644 dsl/src/dsl_provide.rs delete mode 100644 dsl/src/dsl_token.rs delete mode 100644 output/src/ops/align.rs delete mode 100644 output/src/ops/bsp.rs delete mode 100644 output/src/ops/either.rs delete mode 100644 output/src/ops/when.rs create mode 100644 output/src/ops_dsl.rs diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs new file mode 100644 index 0000000..7922d26 --- /dev/null +++ b/dsl/src/dsl_ast.rs @@ -0,0 +1,70 @@ +use crate::*; +use std::sync::Arc; +use std::fmt::{Debug, Display, Formatter}; + +/// Emits tokens. +pub trait Ast: Debug { + fn peek (&self) -> Option; + fn next (&mut self) -> Option; + fn rest (self) -> Option>; +} + +/// A [Cst] can be used as an [Ast]. +impl<'source: 'static> Ast for Cst<'source> { + fn peek (&self) -> Option { + Cst::peek(self).map(|token|token.value.into()) + } + fn next (&mut self) -> Option { + Iterator::next(self).map(|token|token.value.into()) + } + fn rest (self) -> Option> { + self.peek().is_some().then(||Box::new(self) as Box) + } +} + +#[derive(Clone, Default, Debug)] +pub enum AstValue { + #[default] Nil, + Err(DslError), + Num(usize), + Sym(Arc), + Key(Arc), + Str(Arc), + Exp(Arc>), +} + +impl std::fmt::Display for AstValue { + fn fmt (&self, out: &mut Formatter) -> Result<(), std::fmt::Error> { + use AstValue::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(e) => format!("{e:?}"), + }) + } +} + +impl<'source: 'static> From> for AstValue { + fn from (other: CstValue<'source>) -> Self { + use CstValue::*; + match other { + Nil => Self::Nil, + Err(e) => Self::Err(e), + Num(u) => Self::Num(u), + Sym(s) => Self::Sym(s.into()), + Key(s) => Self::Key(s.into()), + Str(s) => Self::Str(s.into()), + Exp(_, s) => Self::Exp(Arc::new(s.into())), + } + } +} + +impl<'source: 'static> Into> for Cst<'source> { + fn into (self) -> Box { + Box::new(self) + } +} diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs new file mode 100644 index 0000000..a45e77a --- /dev/null +++ b/dsl/src/dsl_cst.rs @@ -0,0 +1,291 @@ +use crate::*; + +/// Provides a native [Iterator] API over [CstConstIter], +/// emitting [CstToken] items. +/// +/// [Cst::next] returns just the [CstToken] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct Cst<'a>(pub CstConstIter<'a>); + +/// Owns a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [CstToken] and +/// * the source text remaining +/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstConstIter<'a>(pub &'a str); + +/// A CST token, with reference to the source slice. +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstToken<'source> { + pub source: &'source str, + pub start: usize, + pub length: usize, + pub value: CstValue<'source>, +} + +/// The meaning of a CST token. Strip the source from this to get an [AstValue]. +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum CstValue<'source> { + #[default] Nil, + Err(DslError), + Num(usize), + Sym(&'source str), + Key(&'source str), + Str(&'source str), + Exp(usize, Cst<'source>), +} + +impl<'a> Cst<'a> { + pub const fn new (source: &'a str) -> Self { + Self(CstConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} + +impl<'a> Iterator for Cst<'a> { + type Item = CstToken<'a>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{self.0 = rest; item}) + } +} + +impl<'a> From<&'a str> for Cst<'a> { + fn from (source: &'a str) -> Self{ + Self(CstConstIter(source)) + } +} + +impl<'a> From> for Cst<'a> { + fn from (source: CstConstIter<'a>) -> Self{ + Self(source) + } +} + +/// Implement the const iterator pattern. +#[macro_export] macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } + } +} + +const_iter!(<'a>|self: CstConstIter<'a>| => CstToken<'a> => self.next_mut().map(|(result, _)|result)); + +impl<'a> From<&'a str> for CstConstIter<'a> { + fn from (source: &'a str) -> Self{ + Self::new(source) + } +} + +impl<'a> CstConstIter<'a> { + pub const fn new (source: &'a str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(CstToken<'a>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(CstToken<'a>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + +/// Static iteration helper. +#[macro_export] macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +pub const fn peek_src <'a> (source: &'a str) -> Option> { + use CstValue::*; + let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); + iterate!(char_indices(source) => (start, c) => token = match token.value() { + Err(_) => return Some(token), + Nil => match c { + ' '|'\n'|'\r'|'\t' => + token.grow(), + '(' => + CstToken::new(source, start, 1, Exp(1, Cst::new(str_range(source, start, start + 1)))), + '"' => + CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), + '0'..='9' => + CstToken::new(source, start, 1, match to_digit(c) { + Ok(c) => CstValue::Num(c), + Result::Err(e) => CstValue::Err(e) + }), + _ => token.error(Unexpected(c)) + }, + Str(_) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, + Num(n) => match c { + '0'..='9' => token.grow_num(n, c), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Sym(_) => match c { + 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Key(_) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Exp(depth, _) => match depth { + 0 => return Some(token.grow_exp()), + _ => match c { + ')' => token.grow_out(), + '(' => token.grow_in(), + _ => token.grow_exp(), + } + }, + }); + match token.value() { + Nil => None, + _ => Some(token), + } +} + +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) +} + +impl<'source> CstToken<'source> { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: CstValue<'source> + ) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: CstValue<'source>) -> Self { + Self { value, ..self } + } + pub const fn value (&self) -> CstValue { + self.value + } + pub const fn error (self, error: DslError) -> Self { + Self { value: CstValue::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 { + use CstValue::*; + match to_digit(c) { + Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, + Result::Err(e) => Self { value: Err(e), ..self.grow() }, + } + } + pub const fn grow_key (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Key(token.slice_source(self.source))) + } + pub const fn grow_sym (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.source))) + } + pub const fn grow_str (self) -> Self { + use CstValue::*; + let token = self.grow(); + token.with_value(Str(token.slice_source(self.source))) + } + pub const fn grow_exp (self) -> Self { + use CstValue::*; + let token = self.grow(); + if let Exp(depth, _) = token.value { + token.with_value(Exp(depth, Cst::new(token.slice_source_exp(self.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + if let CstValue::Exp(depth, source) = token.value { + token.with_value(CstValue::Exp(depth.saturating_add(1), source)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let CstValue::Exp(depth, source) = token.value { + if depth > 0 { + token.with_value(CstValue::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +impl<'source> std::fmt::Display for CstValue<'source> { + fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + use CstValue::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(_, e) => format!("{e:?}"), + }) + } +} diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs new file mode 100644 index 0000000..283d79a --- /dev/null +++ b/dsl/src/dsl_domain.rs @@ -0,0 +1,64 @@ +use crate::*; + +pub trait Eval { + fn eval (&self, input: Input) -> Perhaps; +} + +/// May construct [Self] from token stream. +pub trait Dsl: Sized { + type State; + fn try_provide (state: Self::State, source: impl Ast) -> Perhaps; + fn provide >, F: Fn()->E> ( + state: Self::State, source: impl Ast, error: F + ) -> Usually { + let next = source.peek().clone(); + if let Some(value) = Self::try_provide(state, source)? { + Ok(value) + } else { + Result::Err(format!("dsl: {}: {next:?}", error().into()).into()) + } + } +} + +//pub trait Give<'state, 'source, Type> { + ///// Implement this to be able to [Give] [Type] from the [Cst]. + ///// Advance the stream if returning `Ok>`. + //fn give (&'state self, words: Cst<'source>) -> Perhaps; + ///// Return custom error on [None]. + //fn give_or_fail >, F: Fn()->E> ( + //&'state self, mut words: Cst<'source>, error: F + //) -> Usually { + //let next = words.peek().map(|x|x.value).clone(); + //if let Some(value) = Give::::give(self, words)? { + //Ok(value) + //} else { + //Result::Err(format!("give: {}: {next:?}", error().into()).into()) + //} + //} +//} +//#[macro_export] macro_rules! give { + //($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { + //impl Give<$Type> for $State { + //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { + //let $state = self; + //$expr + //} + //} + //}; + //($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { + //impl)++ $(, $Arg)*> Give<$Type> for State { + //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { + //let $state = self; + //$expr + //} + //} + //} +//} +/////// Implement the [Give] trait, which boils down to +/////// specifying two types and providing an expression. +//#[macro_export] macro_rules! from_dsl { + //($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { + //give! { $Type|$state:$State,$words|$expr } + //}; +//} + diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs deleted file mode 100644 index 63bc567..0000000 --- a/dsl/src/dsl_parse.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::*; - -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -/// Provides a native [Iterator] API over the [ConstIntoIter] [SourceIter] -/// [TokenIter::next] returns just the [Token] and mutates `self`, -/// instead of returning an updated version of the struct as [SourceIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct TokenIter<'a>( - pub SourceIter<'a> -); - -impl<'a> TokenIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(SourceIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'a> Iterator for TokenIter<'a> { - type Item = Token<'a>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{self.0 = rest; item}) - } -} - -impl<'a> From<&'a str> for TokenIter<'a> { - fn from (source: &'a str) -> Self{ - Self(SourceIter(source)) - } -} - -impl<'a> From> for TokenIter<'a> { - fn from (source: SourceIter<'a>) -> Self{ - Self(source) - } -} - -/// Owns a reference to the source text. -/// [SourceIter::next] emits subsequent pairs of: -/// * a [Token] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'a>(pub &'a str); - -const_iter!(<'a>|self: SourceIter<'a>| => Token<'a> => self.next_mut().map(|(result, _)|result)); - -impl<'a> From<&'a str> for SourceIter<'a> { - fn from (source: &'a str) -> Self{ - Self::new(source) - } -} - -impl<'a> SourceIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(Token<'a>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(Token<'a>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'a> (source: &'a str) -> Option> { - let mut token: Token<'a> = Token::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - Token::new(source, start, 1, Exp(1, TokenIter::new(str_range(source, start, start + 1)))), - '"' => - Token::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - Token::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - Token::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - Token::new(source, start, 1, match to_digit(c) { - Ok(c) => Value::Num(c), - Result::Err(e) => Value::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} diff --git a/dsl/src/dsl_provide.rs b/dsl/src/dsl_provide.rs deleted file mode 100644 index 34dabd6..0000000 --- a/dsl/src/dsl_provide.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; - -///// Implement the [Give] trait, which boils down to -///// specifying two types and providing an expression. -#[macro_export] macro_rules! from_dsl { - ($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { - take! { $Type|$state:$State,$words|$expr } - }; -} - -/// [Take]s instances of [Type] given [TokenIter]. -pub trait Give<'state, Type> { - /// Implement this to be able to [Give] [Type] from the [TokenIter]. - /// Advance the stream if returning `Ok>`. - fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps; - /// Return custom error on [None]. - fn give_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( - &self, mut words: TokenIter<'source>, error: F - ) -> Usually { - let next = words.peek().map(|x|x.value).clone(); - if let Some(value) = Give::::give(self, words)? { - Ok(value) - } else { - Result::Err(format!("give: {}: {next:?}", error().into()).into()) - } - } -} -#[macro_export] macro_rules! give { - () => { - impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { - fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps { - Type::take(self, words) - } - } - }; - (box) => { - //impl<'state, T, Type: Take<'state, Box>> Give<'state, Box> for Box { - //fn give (&self, mut words:TokenIter<'source>) -> Perhaps> { - //Type::take(self, words) - //} - //} - }; - ($Type:ty: $State:ty) => { - impl<'state, $Type: Take<'state, $State>> Give<'state, $Type> for $State { - fn give <'source: 'state> (&self, mut words:TokenIter<'source>) -> Perhaps<$Type> { - $Type::take(self, words) - } - } - }; - ($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { - impl<'state> Give<'state, $Type> for $State { - fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { - let $state = self; - $expr - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { - impl<'state, State: $(Give<'state, $Arg> +)* 'state $(, $Arg)*> Give<'state, $Type> for State { - fn give <'source: 'state> (&self, mut $words:TokenIter<'source>) -> Perhaps<$Type> { - let $state = self; - $expr - } - } - } -} -/// [Give]s instances of [Self] given [TokenIter]. -pub trait Take<'state, State>: Sized { - /// Implement this to be able to [Take] [Self] from the [TokenIter]. - /// Advance the stream if returning `Ok>`. - fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps; - /// Return custom error on [None]. - fn take_or_fail <'source: 'state, E: Into>, F: Fn()->E> ( - state: &State, mut words:TokenIter<'source>, error: F - ) -> Usually { - let next = words.peek().map(|x|x.value).clone(); - if let Some(value) = Take::::take(state, words)? { - Ok(value) - } else { - Result::Err(format!("take: {}: {next:?}", error().into()).into()) - } - } -} -#[macro_export] macro_rules! take { - () => { - impl<'state, Type: 'state, State: Give<'state, Type> + 'state> Take<'state, State> for Type { - fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - (box) => { - impl<'state, T, State: Give<'state, Box> + 'state> Take<'state, State> for Box { - fn take <'source: 'state> (state: &State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:ty:$State:ty) => { - impl<'state> Take<'state, $State> for $Type { - fn take <'source: 'state> (state: &$State, mut words:TokenIter<'source>) -> Perhaps { - state.give(words) - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { - impl<'state, - State: Give<'state, bool> + $(Give<'state, $Arg> + )* 'state - $(, $Arg: Take<'state, State> + 'state)* - > Take<'state, State> for $Type { - fn take <'source: 'state> ($state: &State, mut $words:TokenIter<'source>) -> Perhaps { - $expr - } - } - }; - ($Type:path$(,$Arg:ident)*|$state:ident:$State:path,$words:ident|$expr:expr) => { - impl<'state $(, $Arg: 'state)*> Take<'state, $State> for $Type { - fn take <'source: 'state> ($state: &$State, mut $words:TokenIter<'source>) -> Perhaps { - $expr - } - } - }; -} - -// auto impl graveyard: - -//impl<'state, State: Give, Type: 'state> Take<'state, State> for Type { - //fn take <'state> (state: &State, mut words:TokenIter<'source>) - //-> Perhaps - //{ - //state.take(words) - //} -//} - -//#[cfg(feature="dsl")] -//impl<'state, E: 'state, State: Give<'state, Box + 'state>>> -//Take<'state, State> for Box + 'state> { - //fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { - //state.give(words) - //} -//} - -impl<'state, Type: Take<'state, State>, State> Give<'state, Type> for State { - fn give <'source: 'state> (&self, words: TokenIter<'source>) -> Perhaps { - Type::take(self, words) - } -} diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs deleted file mode 100644 index d45203b..0000000 --- a/dsl/src/dsl_token.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::*; - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Token<'source> { - pub source: &'source str, - pub start: usize, - pub length: usize, - pub value: Value<'source>, -} - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum Value<'source> { - #[default] Nil, - Err(DslError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, TokenIter<'source>), -} - -impl<'source> std::fmt::Display for Value<'source> { - fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) - } -} - -impl<'source> Token<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: Value<'source> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: Value<'source>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> Value { - self.value - } - pub const fn error (self, error: DslError) -> Self { - Self { value: Value::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) { - Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let Exp(depth, _) = token.value { - token.with_value(Exp(depth, TokenIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - token.with_value(Value::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { - if depth > 0 { - token.with_value(Value::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 712eced..0cd66c8 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -42,13 +42,12 @@ pub(crate) use std::fmt::Debug; 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::Value::*; pub(crate) use self::DslError::*; +mod dsl_ast; pub use self::dsl_ast::*; +mod dsl_cst; pub use self::dsl_cst::*; +mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; -mod dsl_token; pub use self::dsl_token::*; -mod dsl_parse; pub use self::dsl_parse::*; -mod dsl_provide; pub use self::dsl_provide::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 9c08545..3610f36 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,139 +1,67 @@ use crate::*; use std::marker::PhantomData; - -/// [Input] state that can be matched against a [Value]. -pub trait DslInput: Input { fn matches_dsl (&self, token: &str) -> bool; } - -/// A pre-configured mapping of input events to commands. -pub trait KeyMap<'k, S, C: Take<'k, S> + Command, I: DslInput> { - /// Try to find a command that matches the current input event. - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps; +/// List of input layers with optional conditional filters. +#[derive(Default, Debug)] pub struct InputLayers(Vec>); +#[derive(Default, Debug)] pub struct InputLayer{ + __: PhantomData, + condition: Option>, + bindings: Box, } - -/// A [SourceIter] can be a [KeyMap]. -impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for SourceIter<'k> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - let mut iter = self.clone(); - while let Some((token, rest)) = iter.next() { - iter = rest; - match token { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut exp_iter = exp_iter.clone(); - match exp_iter.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, exp_iter)? { - return Ok(Some(command)) - } - } - }, - _ => panic!("invalid config (expected symbol)") - } - }, - _ => panic!("invalid config (expected expression)") - } - } - Ok(None) - } -} - -/// A [TokenIter] can be a [KeyMap]. -impl<'k, S, C: Take<'k, S> + Command, I: DslInput> KeyMap<'k, S, C, I> for TokenIter<'k> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - let mut iter = self.clone(); - while let Some(next) = iter.next() { - match next { - Token { value: Value::Exp(0, exp_iter), .. } => { - let mut e = exp_iter.clone(); - match e.next() { - Some(Token { value: Value::Sym(binding), .. }) => { - if input.matches_dsl(binding) { - if let Some(command) = Take::take(state, e)? { - return Ok(Some(command)) - } - } - }, - _ => panic!("invalid config (expected symbol, got: {exp_iter:?})") - } - }, - _ => panic!("invalid config (expected expression, got: {next:?})") - } - } - Ok(None) - } -} - -pub type InputCondition<'k, S> = BoxUsually + Send + Sync + 'k>; - -/// A collection of pre-configured mappings of input events to commands, -/// which may be made available subject to given conditions. -pub struct InputMap<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> { - __: PhantomData<&'k (S, C, I)>, - pub layers: Vec<(InputCondition<'k, S>, M)>, -} - -impl<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> Default for InputMap<'k, S, C, I, M>{ - fn default () -> Self { - Self { __: PhantomData, layers: vec![] } - } -} - -impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> -InputMap<'k, S, C, I, M> { - pub fn new (keymap: M) -> Self { - Self::default().layer(keymap) - } - pub fn layer (mut self, keymap: M) -> Self { - self.add_layer(keymap); - self - } - pub fn add_layer (&mut self, keymap: M) -> &mut Self { - self.add_layer_if(Box::new(|_|Ok(true)), keymap); - self - } - pub fn layer_if (mut self, condition: InputCondition<'k, S>, keymap: M) -> Self { - self.add_layer_if(condition, keymap); - self - } - pub fn add_layer_if (&mut self, condition: InputCondition<'k, S>, keymap: M) -> &mut Self { - self.layers.push((Box::new(condition), keymap)); +impl InputLayers { + pub fn new (layer: Box) -> Self + { Self(vec![]).layer(layer) } + pub fn layer (mut self, layer: Box) -> Self + { self.add_layer(layer); self } + pub fn layer_if (mut self, condition: Box, layer: Box) -> Self + { self.add_layer_if(Some(condition), layer); self } + pub fn add_layer (&mut self, layer: Box) -> &mut Self + { self.add_layer_if(None, layer.into()); self } + pub fn add_layer_if ( + &mut self, + condition: Option>, + bindings: Box + ) -> &mut Self { + self.0.push(InputLayer { condition, bindings, __: Default::default() }); self } } - -impl<'k, S, C: Command + Take<'k, S>, I: DslInput, M: KeyMap<'k, S, C, I>> -std::fmt::Debug for InputMap<'k, S, C, I, M> { - 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<'k, - S, - C: Command + Take<'k, S>, - I: DslInput, - M: KeyMap<'k, S, C, I> -> KeyMap<'k, S, C, I> for InputMap<'k, S, C, I, M> { - fn keybind_resolve (&self, state: &S, input: &I) -> Perhaps { - for (condition, keymap) in self.layers.iter() { - if !condition(state)? { - continue - } - if let Some(command) = keymap.keybind_resolve(state, input)? { +impl, I: Input, O: Command> Eval for InputLayers { + fn eval (&self, input: I) -> Perhaps { + for layer in self.0.iter() { + if let Some(command) = Ok(if let Some(condition) = layer.condition.as_ref() { + if self.matches(condition)? { + self.state().eval(*self)? + } else { + None + } + } else { + self.state().eval(*self)? + })? { return Ok(Some(command)) } } + } +} +/// [Input] state that can be matched against a [CstValue]. +pub trait InputState: Input { + fn state (&self) -> &S; + fn matches (&self, condition: &Box) -> Usually; +} +impl, I: InputState, O: Command> Eval +for InputLayer { + fn eval (&self, input: I) -> Perhaps { + if let Some(AstToken::Exp(exp)) = iter.peek() { + let mut e = exp.clone(); + if let Some(AstToken::Sym(binding)) = e.next() + && input.matches_dsl(binding) + && let Some(command) = Dsl::from_dsl(input.state(), e)? { + return Ok(Some(command)) + } else { + unreachable!("InputLayer: expected symbol, got: {e:?}") + } + } else { + panic!("InputLayer: expected expression, got: {self:?}") + } Ok(None) } } diff --git a/output/src/lib.rs b/output/src/lib.rs index 394f332..30e8fff 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,8 +3,16 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use tengri_core::*; pub(crate) use std::marker::PhantomData; -#[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(feature = "dsl")] +pub(crate) use ::tengri_dsl::*; + +mod space; pub use self::space::*; + +mod ops; pub use self::ops::*; + +#[cfg(feature = "dsl")] mod ops_dsl; + +mod output; pub use self::output::*; + #[cfg(test)] mod test; diff --git a/output/src/ops.rs b/output/src/ops.rs index 7b8010e..cfeb23e 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -1,10 +1,382 @@ -//mod reduce; pub use self::reduce::*; -mod align; pub use self::align::*; -mod bsp; pub use self::bsp::*; -mod either; pub use self::either::*; +//! Transform: +//! ``` +//! 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]); +//! ``` +//! Align: +//! ``` +//! 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); +//! }; +//! +//! let four = ||Fixed::xy(4, 4, ""); +//! test(area, &Align::nw(four()), [10, 10, 4, 4]); +//! test(area, &Align::n(four()), [18, 10, 4, 4]); +//! test(area, &Align::ne(four()), [26, 10, 4, 4]); +//! test(area, &Align::e(four()), [26, 18, 4, 4]); +//! test(area, &Align::se(four()), [26, 26, 4, 4]); +//! test(area, &Align::s(four()), [18, 26, 4, 4]); +//! test(area, &Align::sw(four()), [10, 26, 4, 4]); +//! test(area, &Align::w(four()), [10, 18, 4, 4]); +//! +//! let two_by_four = ||Fixed::xy(4, 2, ""); +//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); +//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); +//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); +//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); +//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); +//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); +//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); +//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); +//! ``` + +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 when; pub use self::when::*; + +/// Renders multiple things on top of each other, +#[macro_export] macro_rules! lay { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} +} + +/// Stack southward. +#[macro_export] macro_rules! col { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; +} + +/// Stack northward. +#[macro_export] macro_rules! col_up { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} +} + +/// Stack eastward. +#[macro_export] macro_rules! row { + ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; +} + +/// Show an item only when a condition is true. +pub struct When(pub bool, pub A); +impl When { + /// Create a binary condition. + pub const fn new (c: bool, a: A) -> Self { Self(c, a) } +} +impl> Content for When { + fn layout (&self, to: E::Area) -> E::Area { + let Self(cond, item) = self; + let mut area = E::Area::zero(); + if *cond { + let item_area = item.layout(to); + area[0] = item_area.x(); + area[1] = item_area.y(); + area[2] = item_area.w(); + area[3] = item_area.h(); + } + area.into() + } + fn render (&self, to: &mut E) { + let Self(cond, item) = self; + if *cond { item.render(to) } + } +} + +/// Show one item if a condition is true and another if the condition is false +pub struct Either(pub bool, pub A, pub B); +impl Either { + /// Create a ternary view condition. + pub const fn new (c: bool, a: A, b: B) -> Self { Self(c, a, b) } +} +impl, B: Render> Content for Either { + fn layout (&self, to: E::Area) -> E::Area { + let Self(cond, a, b) = self; + if *cond { a.layout(to) } else { b.layout(to) } + } + fn render (&self, to: &mut E) { + let Self(cond, a, b) = self; + if *cond { a.render(to) } else { b.render(to) } + } +} + +/// 9th of area to place. +#[derive(Debug, Copy, Clone, Default)] +pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } + +pub struct Align(Alignment, A); + +impl Align { + #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } + #[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) } + #[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) } + #[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) } + #[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) } + #[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) } + #[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) } + #[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) } + #[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) } + #[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) } + #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } +} + +impl> Content for Align { + fn content (&self) -> impl Render + '_ { + &self.1 + } + fn layout (&self, on: E::Area) -> E::Area { + use Alignment::*; + let it = Render::layout(&self.content(), on).xywh(); + let cx = on.x()+(on.w().minus(it.w())/2.into()); + let cy = on.y()+(on.h().minus(it.h())/2.into()); + let fx = (on.x()+on.w()).minus(it.w()); + let fy = (on.y()+on.h()).minus(it.h()); + let [x, y] = match self.0 { + Center => [cx, cy], + X => [cx, it.y()], + Y => [it.x(), cy], + NW => [on.x(), on.y()], + N => [cx, on.y()], + NE => [fx, on.y()], + W => [on.x(), cy], + E => [fx, cy], + SW => [on.x(), fy], + S => [cx, fy], + SE => [fx, fy], + }.into(); + [x, y, it.w(), it.h()].into() + } + fn render (&self, to: &mut E) { + to.place(Content::layout(self, to.area()), &self.content()) + } +} + +/// A split or layer. +pub struct Bsp( + pub(crate) Direction, + pub(crate) A, + pub(crate) B, +); +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) } + #[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) } + #[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) } + #[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) } + #[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) } +} +impl, B: Content> Content for Bsp { + fn layout (&self, outer: E::Area) -> E::Area { let [_, _, c] = self.areas(outer); c } + fn render (&self, to: &mut E) { + let [area_a, area_b, _] = self.areas(to.area()); + let (a, b) = self.contents(); + match self.0 { + Below => { to.place(area_a, a); to.place(area_b, b); }, + _ => { to.place(area_b, b); to.place(area_a, a); } + } + } +} +impl, B: Content> BspAreas for Bsp { + fn direction (&self) -> Direction { self.0 } + fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } +} +pub trait BspAreas, B: Content> { + fn direction (&self) -> Direction; + fn contents (&self) -> (&A, &B); + fn areas (&self, outer: E::Area) -> [E::Area;3] { + let direction = self.direction(); + let [x, y, w, h] = outer.xywh(); + let (a, b) = self.contents(); + let [aw, ah] = a.layout(outer).wh(); + let [bw, bh] = b.layout(match direction { + Above | Below => outer, + South => [x, y + ah, w, h.minus(ah)].into(), + North => [x, y, w, h.minus(ah)].into(), + East => [x + aw, y, w.minus(aw), h].into(), + West => [x, y, w.minus(aw), h].into(), + }).wh(); + match direction { + Above | Below => { + let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]); + let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + South => { + 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()] + }, + North => { + let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); + let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah]; + let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + East => { + let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); + let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + West => { + let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); + let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah]; + let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh]; + [a.into(), b.into(), [x, y, w, h].into()] + }, + } + } +} + +/// 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) } + } + 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) } + } + 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/output/src/ops/align.rs b/output/src/ops/align.rs deleted file mode 100644 index ffd5b48..0000000 --- a/output/src/ops/align.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Aligns things to the container. Comes with caveats. -//! ``` -//! 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); -//! }; -//! -//! let four = ||Fixed::xy(4, 4, ""); -//! test(area, &Align::nw(four()), [10, 10, 4, 4]); -//! test(area, &Align::n(four()), [18, 10, 4, 4]); -//! test(area, &Align::ne(four()), [26, 10, 4, 4]); -//! test(area, &Align::e(four()), [26, 18, 4, 4]); -//! test(area, &Align::se(four()), [26, 26, 4, 4]); -//! test(area, &Align::s(four()), [18, 26, 4, 4]); -//! test(area, &Align::sw(four()), [10, 26, 4, 4]); -//! test(area, &Align::w(four()), [10, 18, 4, 4]); -//! -//! let two_by_four = ||Fixed::xy(4, 2, ""); -//! test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]); -//! test(area, &Align::n(two_by_four()), [18, 10, 4, 2]); -//! test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]); -//! test(area, &Align::e(two_by_four()), [26, 19, 4, 2]); -//! test(area, &Align::se(two_by_four()), [26, 28, 4, 2]); -//! test(area, &Align::s(two_by_four()), [18, 28, 4, 2]); -//! test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]); -//! test(area, &Align::w(two_by_four()), [10, 19, 4, 2]); -//! ``` -use crate::*; -#[derive(Debug, Copy, Clone, Default)] -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, State: Give<'state, A>, A: 'state> Take<'state, State> for Align { - fn take <'source: 'state> (state: &State, words: TokenIter<'source>) -> Perhaps { - todo!() - } -} -//give!(Align, A|state, words|Ok(Some(match words.peek() { - //Some(Token { value: Value::Key(key), .. }) => match key { - //"align/c"|"align/x"|"align/y"| - //"align/n"|"align/s"|"align/e"|"al;qign/w"| - //"align/nw"|"align/sw"|"align/ne"|"align/se" => { - //let _ = words.next().unwrap(); - //let content = Take::take_or_fail(state, &mut words.clone(), ||"expected content")?; - //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) - //}, - //_ => return Ok(None) -//}))); - -impl Align { - #[inline] pub const fn c (a: A) -> Self { Self(Alignment::Center, a) } - #[inline] pub const fn x (a: A) -> Self { Self(Alignment::X, a) } - #[inline] pub const fn y (a: A) -> Self { Self(Alignment::Y, a) } - #[inline] pub const fn n (a: A) -> Self { Self(Alignment::N, a) } - #[inline] pub const fn s (a: A) -> Self { Self(Alignment::S, a) } - #[inline] pub const fn e (a: A) -> Self { Self(Alignment::E, a) } - #[inline] pub const fn w (a: A) -> Self { Self(Alignment::W, a) } - #[inline] pub const fn nw (a: A) -> Self { Self(Alignment::NW, a) } - #[inline] pub const fn sw (a: A) -> Self { Self(Alignment::SW, a) } - #[inline] pub const fn ne (a: A) -> Self { Self(Alignment::NE, a) } - #[inline] pub const fn se (a: A) -> Self { Self(Alignment::SE, a) } -} - -impl> Content for Align { - fn content (&self) -> impl Render + '_ { - &self.1 - } - fn layout (&self, on: E::Area) -> E::Area { - use Alignment::*; - let it = Render::layout(&self.content(), on).xywh(); - let cx = on.x()+(on.w().minus(it.w())/2.into()); - let cy = on.y()+(on.h().minus(it.h())/2.into()); - let fx = (on.x()+on.w()).minus(it.w()); - let fy = (on.y()+on.h()).minus(it.h()); - let [x, y] = match self.0 { - Center => [cx, cy], - X => [cx, it.y()], - Y => [it.x(), cy], - NW => [on.x(), on.y()], - N => [cx, on.y()], - NE => [fx, on.y()], - W => [on.x(), cy], - E => [fx, cy], - SW => [on.x(), fy], - S => [cx, fy], - SE => [fx, fy], - }.into(); - [x, y, it.w(), it.h()].into() - } - fn render (&self, to: &mut E) { - to.place(Content::layout(self, to.area()), &self.content()) - } -} diff --git a/output/src/ops/bsp.rs b/output/src/ops/bsp.rs deleted file mode 100644 index e714f8e..0000000 --- a/output/src/ops/bsp.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::*; -pub use Direction::*; -/// A split or layer. -pub struct Bsp( - pub(crate) Direction, - pub(crate) A, - pub(crate) B, -); -impl, B: Content> Content for Bsp { - fn layout (&self, outer: E::Area) -> E::Area { - let [_, _, c] = self.areas(outer); - c - } - fn render (&self, to: &mut E) { - let [area_a, area_b, _] = self.areas(to.area()); - let (a, b) = self.contents(); - match self.0 { - Below => { to.place(area_a, a); to.place(area_b, b); }, - _ => { to.place(area_b, b); to.place(area_a, a); } - } - } -} -#[cfg(feature = "dsl")] take!(Bsp, A, B|state, words|Ok(if let Some(Token { - value: Value::Key("bsp/n"|"bsp/s"|"bsp/e"|"bsp/w"|"bsp/a"|"bsp/b"), - .. -}) = words.peek() { - if let Value::Key(key) = words.next().unwrap().value() { - let base = words.clone(); - let a: A = state.give_or_fail(words, ||"bsp: expected content 1")?; - let b: B = state.give_or_fail(words, ||"bsp: expected content 2")?; - return Ok(Some(match key { - "bsp/n" => Self::n(a, b), - "bsp/s" => Self::s(a, b), - "bsp/e" => Self::e(a, b), - "bsp/w" => Self::w(a, b), - "bsp/a" => Self::a(a, b), - "bsp/b" => Self::b(a, b), - _ => unreachable!(), - })) - } else { - unreachable!() - } -} else { - 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) } - #[inline] pub const fn e (a: A, b: B) -> Self { Self(East, a, b) } - #[inline] pub const fn w (a: A, b: B) -> Self { Self(West, a, b) } - #[inline] pub const fn a (a: A, b: B) -> Self { Self(Above, a, b) } - #[inline] pub const fn b (a: A, b: B) -> Self { Self(Below, a, b) } -} -pub trait BspAreas, B: Content> { - fn direction (&self) -> Direction; - fn contents (&self) -> (&A, &B); - fn areas (&self, outer: E::Area) -> [E::Area;3] { - let direction = self.direction(); - let [x, y, w, h] = outer.xywh(); - let (a, b) = self.contents(); - let [aw, ah] = a.layout(outer).wh(); - let [bw, bh] = b.layout(match direction { - Above | Below => outer, - South => [x, y + ah, w, h.minus(ah)].into(), - North => [x, y, w, h.minus(ah)].into(), - East => [x + aw, y, w.minus(aw), h].into(), - West => [x, y, w.minus(aw), h].into(), - }).wh(); - match direction { - Above | Below => { - let [x, y, w, h] = outer.center_xy([aw.max(bw), ah.max(bh)]); - let a = [(x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [(x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - South => { - 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()] - }, - North => { - let [x, y, w, h] = outer.center_xy([aw.max(bw), ah + bh]); - let a = [(x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah]; - let b = [(x + (w/2.into())).minus(bw/2.into()), y, bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - East => { - let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); - let a = [x, (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - West => { - let [x, y, w, h] = outer.center_xy([aw + bw, ah.max(bh)]); - let a = [x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah]; - let b = [x, (y + h/2.into()).minus(bh/2.into()), bw, bh]; - [a.into(), b.into(), [x, y, w, h].into()] - }, - } - } -} -impl, B: Content> BspAreas for Bsp { - fn direction (&self) -> Direction { self.0 } - fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } -} -/// Renders multiple things on top of each other, -#[macro_export] macro_rules! lay { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} -} -/// Stack southward. -#[macro_export] macro_rules! col { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; -} -/// Stack northward. -#[macro_export] macro_rules! col_up { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} -} -/// Stack eastward. -#[macro_export] macro_rules! row { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; -} diff --git a/output/src/ops/either.rs b/output/src/ops/either.rs deleted file mode 100644 index 29ab0e8..0000000 --- a/output/src/ops/either.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::*; - -/// Show one item if a condition is true and another if the condition is false -pub struct Either(pub bool, pub A, pub B); -impl Either { - /// Create a ternary condition. - pub const fn new (c: bool, a: A, b: B) -> Self { - Self(c, a, b) - } -} -#[cfg(feature = "dsl")] take!(Either, A, B|state, words|Ok( -if let Some(Token { value: Value::Key("either"), .. }) = words.peek() { - let base = words.clone(); - let _ = words.next().unwrap(); - return Ok(Some(Self( - state.give_or_fail(words, ||"either: no condition")?, - state.give_or_fail(words, ||"either: no content 1")?, - state.give_or_fail(words, ||"either: no content 2")?, - ))) -} else { - None -})); -impl, B: Render> Content for Either { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, a, b) = self; - if *cond { a.layout(to) } else { b.layout(to) } - } - fn render (&self, to: &mut E) { - let Self(cond, a, b) = self; - if *cond { a.render(to) } else { b.render(to) } - } -} diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index 593a518..e69de29 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -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/output/src/ops/when.rs b/output/src/ops/when.rs deleted file mode 100644 index 6324900..0000000 --- a/output/src/ops/when.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::*; -/// Show an item only when a condition is true. -pub struct When(pub bool, pub A); -impl When { - /// Create a binary condition. - pub const fn new (c: bool, a: A) -> Self { - Self(c, a) - } -} -#[cfg(feature = "dsl")]take!(When, A|state, words|Ok(Some(match words.peek() { - Some(Token { value: Value::Key("when"), .. }) => { - let _ = words.next(); - let base = words.clone(); - let cond = state.give_or_fail(words, ||"cond: no condition")?; - let cont = state.give_or_fail(words, ||"cond: no content")?; - Self(cond, cont) - }, - _ => return Ok(None) -}))); - -impl> Content for When { - fn layout (&self, to: E::Area) -> E::Area { - let Self(cond, item) = self; - let mut area = E::Area::zero(); - if *cond { - let item_area = item.layout(to); - area[0] = item_area.x(); - area[1] = item_area.y(); - area[2] = item_area.w(); - area[3] = item_area.h(); - } - area.into() - } - fn render (&self, to: &mut E) { - let Self(cond, item) = self; - if *cond { item.render(to) } - } -} diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs new file mode 100644 index 0000000..d34cc34 --- /dev/null +++ b/output/src/ops_dsl.rs @@ -0,0 +1,185 @@ +use crate::*; + +impl Eval> for S where + S: Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(match source.peek() { + Some(Value::Key("when")) => Some(Self( + self.provide(source, ||"when: expected condition")?, + self.provide(source, ||"when: expected content")?, + )), + _ => None + }) + } +} + +impl Eval> for S where + S: Eval + Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(match source.peek() { + Some(Value::Key("either")) => Some(Self( + self.provide(source, ||"either: expected condition")?, + self.provide(source, ||"either: expected content 1")?, + self.provide(source, ||"either: expected content 2")? + )), + _ => None + }) + } +} + +impl Eval> for S where + S: Eval + Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(if let Some(Value::Key(key)) = source.peek() { + Some(match key { + "bsp/n" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/n: expected content 1")?; + let b: B = self.provide(source, ||"bsp/n: expected content 2")?; + Self::n(a, b) + }, + "bsp/s" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/s: expected content 1")?; + let b: B = self.provide(source, ||"bsp/s: expected content 2")?; + Self::s(a, b) + }, + "bsp/e" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/e: expected content 1")?; + let b: B = self.provide(source, ||"bsp/e: expected content 2")?; + Self::e(a, b) + }, + "bsp/w" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/w: expected content 1")?; + let b: B = self.provide(source, ||"bsp/w: expected content 2")?; + Self::w(a, b) + }, + "bsp/a" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/a: expected content 1")?; + let b: B = self.provide(source, ||"bsp/a: expected content 2")?; + Self::a(a, b) + }, + "bsp/b" => { + let _ = source.next(); + let a: A = self.provide(source, ||"bsp/b: expected content 1")?; + let b: B = self.provide(source, ||"bsp/b: expected content 2")?; + Self::b(a, b) + }, + _ => return Ok(None), + }) + } else { + None + }) + } +} + +impl Eval> for S where + S: Eval +{ + fn eval (&self, source: I) -> Perhaps { + Ok(if let Some(Value::Key(key)) = source.peek() { + Some(match key { + "align/c" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/c: expected content")?; + Self::c(content) + }, + "align/x" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/x: expected content")?; + Self::x(content) + }, + "align/y" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/y: expected content")?; + Self::y(content) + }, + "align/n" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/n: expected content")?; + Self::n(content) + }, + "align/s" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/s: expected content")?; + Self::s(content) + }, + "align/e" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/e: expected content")?; + Self::e(content) + }, + "align/w" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/w: expected content")?; + Self::w(content) + }, + "align/nw" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/nw: expected content")?; + Self::nw(content) + }, + "align/ne" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/ne: expected content")?; + Self::ne(content) + }, + "align/sw" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/sw: expected content")?; + Self::sw(content) + }, + "align/se" => { + let _ = source.next(); + let content: A = self.provide(source, ||"align/se: expected content")?; + Self::se(content) + }, + _ => return Ok(None), + }) + } else { + None + }) + } +} + + //#[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 + //})); + //#[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 + //})); diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs index 90dc95e..ed9df46 100644 --- a/output/src/space/direction.rs +++ b/output/src/space/direction.rs @@ -1,4 +1,5 @@ use crate::*; +use crate::Direction::*; /// A cardinal direction. #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index ddbc205..b4ab9d7 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -149,10 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl<'state> ::tengri::dsl::Take<'state, #state> for #command_enum { - fn take <'source: 'state> ( - state: &#state, mut words: ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { + impl ::tengri::dsl::Take<#state> for #command_enum { + fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { let mut words = words.clone(); let token = words.next(); todo!()//Ok(match token { #(#matchers)* _ => None }) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index f345e43..873b639 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,10 +85,8 @@ impl ToTokens for ExposeImpl { }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl<'n> ::tengri::dsl::Take<'n, #state> for #t { - fn take <'source: 'n> ( - state: &#state, mut words: ::tengri::dsl::TokenIter<'source> - ) -> Perhaps { + impl ::tengri::dsl::Take<#state> for #t { + fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { Ok(Some(match words.next().map(|x|x.value) { #predefined #(#values)* diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 578bbbf..9815c34 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -43,50 +43,38 @@ impl ToTokens for ViewDef { let self_ty = &block.self_ty; // Expressions are handled by built-in functions // that operate over constants and symbols. - let builtin = builtins_with_boxes_output(quote! { #output }); + let builtin = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { + ::tengri::dsl::Value::Exp(_, expr) => return Ok(Some( + #builtin::take_or_fail(state, 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) => Some(Box::new(Thunk::new( - move||#self_ty::#value(state))))})); + ::tengri::dsl::Value::Sym(#key) => return Ok(Some( + state.#value().boxed() + )), + })); write_quote_to(out, quote! { + // Original user-taked implementation: + #block /// Generated by [tengri_proc]. /// /// 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: 'static> Take<'state, #self_ty> for Box + 'state> { - fn take <'source: 'state> (state: &#self_ty, mut words: TokenIter<'source>) - -> Perhaps + 'state>> - { - //let state = self; + impl<'source, 'state: 'source> + Take<'state, 'source, #self_ty> + for Box + 'state> + { + fn take (state: &'state #self_ty, mut words: Cst<'source>) -> Perhaps { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { - match value { - #(::tengri::dsl::Value::Exp(_, expr) => { - Give::<'state, #builtin>::give(state, expr)?.map(|value|value.boxed()) - },)* - #( - #exposed, - )* - _ => None - } + match value { #(#builtin)* #(#exposed)* _ => None } } else { None }) } } - /// Generated by [tengri_proc]. - /// - /// Delegates the rendering of [#self_ty] to the [#self_ty::view} method, - /// which you will need to implement, e.g. passing a [TokenIter] - /// containing a layout and keybindings config from user dirs. - impl ::tengri::output::Content<#output> for #self_ty { - fn content (&self) -> impl Render<#output> + '_ { - #self_ty::view(self) - } - } - // Original user-taked implementation: - #block }) } } @@ -96,11 +84,11 @@ fn builtins_with_holes () -> impl Iterator { } fn builtins_with_boxes () -> impl Iterator { - builtins_with(quote! { _ }, quote! { Box+'state> }) + builtins_with(quote! { _ }, quote! { Box> }) } fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator { - builtins_with(quote! { _ }, quote! { Box+'state> }) + builtins_with(quote! { _ }, quote! { Box> }) } fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator { From 7097333993c070f9a5bafe651f8f2e4632b8e4e7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 01:30:13 +0300 Subject: [PATCH 100/178] wip: dsl: more rework --- dsl/src/dsl_ast.rs | 54 ++++++++-------- dsl/src/dsl_domain.rs | 26 +++++--- input/src/input_dsl.rs | 86 +++++++++++-------------- output/src/ops_dsl.rs | 138 +++++++++++++++++++---------------------- 4 files changed, 145 insertions(+), 159 deletions(-) diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 7922d26..15c4374 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -2,40 +2,40 @@ use crate::*; use std::sync::Arc; use std::fmt::{Debug, Display, Formatter}; -/// Emits tokens. -pub trait Ast: Debug { - fn peek (&self) -> Option; - fn next (&mut self) -> Option; - fn rest (self) -> Option>; -} - -/// A [Cst] can be used as an [Ast]. -impl<'source: 'static> Ast for Cst<'source> { - fn peek (&self) -> Option { - Cst::peek(self).map(|token|token.value.into()) - } - fn next (&mut self) -> Option { - Iterator::next(self).map(|token|token.value.into()) - } - fn rest (self) -> Option> { - self.peek().is_some().then(||Box::new(self) as Box) - } -} - #[derive(Clone, Default, Debug)] -pub enum AstValue { +pub enum Ast { #[default] Nil, Err(DslError), Num(usize), Sym(Arc), Key(Arc), Str(Arc), - Exp(Arc>), + Exp(Arc>), } -impl std::fmt::Display for AstValue { +/// Emits tokens. +pub trait AstIter: Debug { + fn peek (&self) -> Option; + fn next (&mut self) -> Option; + fn rest (self) -> Option>; +} + +/// A [Cst] can be used as an [Ast]. +impl<'source: 'static> AstIter for Cst<'source> { + fn peek (&self) -> Option { + Cst::peek(self).map(|token|token.value.into()) + } + fn next (&mut self) -> Option { + Iterator::next(self).map(|token|token.value.into()) + } + fn rest (self) -> Option> { + self.peek().is_some().then(||Box::new(self) as Box) + } +} + +impl std::fmt::Display for Ast { fn fmt (&self, out: &mut Formatter) -> Result<(), std::fmt::Error> { - use AstValue::*; + use Ast::*; write!(out, "{}", match self { Nil => String::new(), Err(e) => format!("[error: {e}]"), @@ -48,7 +48,7 @@ impl std::fmt::Display for AstValue { } } -impl<'source: 'static> From> for AstValue { +impl<'source: 'static> From> for Ast { fn from (other: CstValue<'source>) -> Self { use CstValue::*; match other { @@ -63,8 +63,8 @@ impl<'source: 'static> From> for AstValue { } } -impl<'source: 'static> Into> for Cst<'source> { - fn into (self) -> Box { +impl<'source: 'static> Into> for Cst<'source> { + fn into (self) -> Box { Box::new(self) } } diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs index 283d79a..88af556 100644 --- a/dsl/src/dsl_domain.rs +++ b/dsl/src/dsl_domain.rs @@ -1,25 +1,37 @@ use crate::*; pub trait Eval { - fn eval (&self, input: Input) -> Perhaps; + fn try_eval (&self, input: Input) -> Perhaps; + fn eval >, F: Fn()->E> ( + &self, input: Input, error: F + ) -> Usually { + if let Some(value) = self.try_eval(input)? { + Ok(value) + } else { + Result::Err(format!("Eval: {}", error().into()).into()) + } + } } /// May construct [Self] from token stream. -pub trait Dsl: Sized { - type State; - fn try_provide (state: Self::State, source: impl Ast) -> Perhaps; +pub trait Dsl: Sized { + fn try_provide (state: State, source: Ast) -> Perhaps; fn provide >, F: Fn()->E> ( - state: Self::State, source: impl Ast, error: F + state: State, source: Ast, error: F ) -> Usually { - let next = source.peek().clone(); + let next = source.clone(); if let Some(value) = Self::try_provide(state, source)? { Ok(value) } else { - Result::Err(format!("dsl: {}: {next:?}", error().into()).into()) + Result::Err(format!("Dsl: {}: {next:?}", error().into()).into()) } } } +impl, S> Eval for S { + fn try_eval (&self, input: Ast) -> Perhaps { todo!() } +} + //pub trait Give<'state, 'source, Type> { ///// Implement this to be able to [Give] [Type] from the [Cst]. ///// Advance the stream if returning `Ok>`. diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 3610f36..c6d26e1 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,67 +1,53 @@ use crate::*; use std::marker::PhantomData; +use std::fmt::Debug; /// List of input layers with optional conditional filters. #[derive(Default, Debug)] pub struct InputLayers(Vec>); #[derive(Default, Debug)] pub struct InputLayer{ __: PhantomData, - condition: Option>, - bindings: Box, + condition: Option, + binding: Ast, } impl InputLayers { - pub fn new (layer: Box) -> Self - { Self(vec![]).layer(layer) } - pub fn layer (mut self, layer: Box) -> Self - { self.add_layer(layer); self } - pub fn layer_if (mut self, condition: Box, layer: Box) -> Self - { self.add_layer_if(Some(condition), layer); self } - pub fn add_layer (&mut self, layer: Box) -> &mut Self - { self.add_layer_if(None, layer.into()); self } - pub fn add_layer_if ( - &mut self, - condition: Option>, - bindings: Box - ) -> &mut Self { - self.0.push(InputLayer { condition, bindings, __: Default::default() }); + pub fn new (layer: Ast) -> Self { + Self(vec![]).layer(layer) + } + pub fn layer (mut self, layer: Ast) -> Self { + self.add_layer(layer); self + } + pub fn layer_if (mut self, condition: Ast, layer: Ast) -> Self { + self.add_layer_if(Some(condition), layer); self + } + pub fn add_layer (&mut self, layer: Ast) -> &mut Self { + self.add_layer_if(None, layer.into()); self + } + pub fn add_layer_if (&mut self, condition: Option, binding: Ast) -> &mut Self { + self.0.push(InputLayer { condition, binding, __: Default::default() }); self } } -impl, I: Input, O: Command> Eval for InputLayers { - fn eval (&self, input: I) -> Perhaps { - for layer in self.0.iter() { - if let Some(command) = Ok(if let Some(condition) = layer.condition.as_ref() { - if self.matches(condition)? { - self.state().eval(*self)? - } else { - None +impl + Eval, C: Command, I: Debug + Eval> Eval<(S, I), C> for InputLayers { + fn try_eval (&self, (state, input): (S, I)) -> Perhaps { + for InputLayer { condition, binding, .. } in self.0.iter() { + let mut matches = true; + if let Some(condition) = condition { + matches = state.eval(condition.clone(), ||"input: no condition")?; + } + if matches { + if let Ast::Exp(e) = binding { + if let Some(ast) = e.peek() { + if input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? + && let Some(command) = state.try_eval(ast)? { + return Ok(Some(command)) + } + } else { + unreachable!("InputLayer") + } + } else { + panic!("InputLayer: expected expression, got: {input:?}") } - } else { - self.state().eval(*self)? - })? { - return Ok(Some(command)) } } - } -} -/// [Input] state that can be matched against a [CstValue]. -pub trait InputState: Input { - fn state (&self) -> &S; - fn matches (&self, condition: &Box) -> Usually; -} -impl, I: InputState, O: Command> Eval -for InputLayer { - fn eval (&self, input: I) -> Perhaps { - if let Some(AstToken::Exp(exp)) = iter.peek() { - let mut e = exp.clone(); - if let Some(AstToken::Sym(binding)) = e.next() - && input.matches_dsl(binding) - && let Some(command) = Dsl::from_dsl(input.state(), e)? { - return Ok(Some(command)) - } else { - unreachable!("InputLayer: expected symbol, got: {e:?}") - } - } else { - panic!("InputLayer: expected expression, got: {self:?}") - } Ok(None) } } diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index d34cc34..d718b10 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -1,143 +1,131 @@ use crate::*; -impl Eval> for S where - S: Eval + Eval -{ - fn eval (&self, source: I) -> Perhaps { +impl Dsl for When where S: Eval + Eval { + fn try_provide (state: S, source: Ast) -> Perhaps { Ok(match source.peek() { Some(Value::Key("when")) => Some(Self( - self.provide(source, ||"when: expected condition")?, - self.provide(source, ||"when: expected content")?, + state.eval(source, ||"when: expected condition")?, + state.eval(source, ||"when: expected content")?, )), _ => None }) } } -impl Eval> for S where - S: Eval + Eval + Eval -{ - fn eval (&self, source: I) -> Perhaps { +impl Dsl for Either where S: Eval + Eval + Eval { + fn try_provide (state: S, source: Ast) -> Perhaps { Ok(match source.peek() { Some(Value::Key("either")) => Some(Self( - self.provide(source, ||"either: expected condition")?, - self.provide(source, ||"either: expected content 1")?, - self.provide(source, ||"either: expected content 2")? + state.eval(source, ||"either: expected condition")?, + state.eval(source, ||"either: expected content 1")?, + state.eval(source, ||"either: expected content 2")? )), _ => None }) } } -impl Eval> for S where - S: Eval + Eval -{ - fn eval (&self, source: I) -> Perhaps { - Ok(if let Some(Value::Key(key)) = source.peek() { - Some(match key { - "bsp/n" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/n: expected content 1")?; - let b: B = self.provide(source, ||"bsp/n: expected content 2")?; - Self::n(a, b) - }, - "bsp/s" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/s: expected content 1")?; - let b: B = self.provide(source, ||"bsp/s: expected content 2")?; - Self::s(a, b) - }, - "bsp/e" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/e: expected content 1")?; - let b: B = self.provide(source, ||"bsp/e: expected content 2")?; - Self::e(a, b) - }, - "bsp/w" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/w: expected content 1")?; - let b: B = self.provide(source, ||"bsp/w: expected content 2")?; - Self::w(a, b) - }, - "bsp/a" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/a: expected content 1")?; - let b: B = self.provide(source, ||"bsp/a: expected content 2")?; - Self::a(a, b) - }, - "bsp/b" => { - let _ = source.next(); - let a: A = self.provide(source, ||"bsp/b: expected content 1")?; - let b: B = self.provide(source, ||"bsp/b: expected content 2")?; - Self::b(a, b) - }, - _ => return Ok(None), - }) - } else { - None - }) +impl Dsl for Bsp where S: Eval + Eval { + fn try_provide (state: S, source: Ast) -> Perhaps { + Ok(Some(match source.peek() { + Some(Value::Key("bsp/n")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/n: expected content 1")?; + let b: B = state.eval(source, ||"bsp/n: expected content 2")?; + Self::n(a, b) + }, + Some(Value::Key("bsp/s")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/s: expected content 1")?; + let b: B = state.eval(source, ||"bsp/s: expected content 2")?; + Self::s(a, b) + }, + Some(Value::Key("bsp/e")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/e: expected content 1")?; + let b: B = state.eval(source, ||"bsp/e: expected content 2")?; + Self::e(a, b) + }, + Some(Value::Key("bsp/w")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/w: expected content 1")?; + let b: B = state.eval(source, ||"bsp/w: expected content 2")?; + Self::w(a, b) + }, + Some(Value::Key("bsp/a")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/a: expected content 1")?; + let b: B = state.eval(source, ||"bsp/a: expected content 2")?; + Self::a(a, b) + }, + Some(Value::Key("bsp/b")) => { + let _ = source.next(); + let a: A = state.eval(source, ||"bsp/b: expected content 1")?; + let b: B = state.eval(source, ||"bsp/b: expected content 2")?; + Self::b(a, b) + }, + _ => return Ok(None), + })) } } -impl Eval> for S where - S: Eval -{ - fn eval (&self, source: I) -> Perhaps { +impl Dsl for Align where S: Eval { + fn try_provide (state: S, source: Ast) -> Perhaps { Ok(if let Some(Value::Key(key)) = source.peek() { Some(match key { "align/c" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/c: expected content")?; + let content: A = state.eval(source, ||"align/c: expected content")?; Self::c(content) }, "align/x" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/x: expected content")?; + let content: A = state.eval(source, ||"align/x: expected content")?; Self::x(content) }, "align/y" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/y: expected content")?; + let content: A = state.eval(source, ||"align/y: expected content")?; Self::y(content) }, "align/n" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/n: expected content")?; + let content: A = state.eval(source, ||"align/n: expected content")?; Self::n(content) }, "align/s" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/s: expected content")?; + let content: A = state.eval(source, ||"align/s: expected content")?; Self::s(content) }, "align/e" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/e: expected content")?; + let content: A = state.eval(source, ||"align/e: expected content")?; Self::e(content) }, "align/w" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/w: expected content")?; + let content: A = state.eval(source, ||"align/w: expected content")?; Self::w(content) }, "align/nw" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/nw: expected content")?; + let content: A = state.eval(source, ||"align/nw: expected content")?; Self::nw(content) }, "align/ne" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/ne: expected content")?; + let content: A = state.eval(source, ||"align/ne: expected content")?; Self::ne(content) }, "align/sw" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/sw: expected content")?; + let content: A = state.eval(source, ||"align/sw: expected content")?; Self::sw(content) }, "align/se" => { let _ = source.next(); - let content: A = self.provide(source, ||"align/se: expected content")?; + let content: A = state.eval(source, ||"align/se: expected content")?; Self::se(content) }, _ => return Ok(None), From 93b1cf1a5cebb22909c914a40e9685af125d7d42 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 01:45:08 +0300 Subject: [PATCH 101/178] wip: dsl: getting interesting --- output/src/ops_dsl.rs | 180 ++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 94 deletions(-) diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index d718b10..e86a634 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -2,137 +2,129 @@ use crate::*; impl Dsl for When where S: Eval + Eval { fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(match source.peek() { - Some(Value::Key("when")) => Some(Self( - state.eval(source, ||"when: expected condition")?, - state.eval(source, ||"when: expected content")?, - )), - _ => None - }) + if let Ast::Exp(source) = source { + if let Some(Ast::Key(id)) = source.next() && *id == *"when" { + return Ok(Some(Self( + state.eval(source.next().unwrap(), ||"when: expected condition")?, + state.eval(source.next().unwrap(), ||"when: expected content")?, + ))) + } + } + Ok(None) } } impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(match source.peek() { - Some(Value::Key("either")) => Some(Self( - state.eval(source, ||"either: expected condition")?, - state.eval(source, ||"either: expected content 1")?, - state.eval(source, ||"either: expected content 2")? - )), - _ => None - }) + fn try_provide (state: S, mut source: Ast) -> Perhaps { + if let Ast::Exp(mut source) = source { + if let Some(Ast::Key(id)) = source.next() && *id == *"either" { + return Ok(Some(Self( + state.eval(source.next().unwrap(), ||"either: expected condition")?, + state.eval(source.next().unwrap(), ||"either: expected content 1")?, + state.eval(source.next().unwrap(), ||"either: expected content 2")?, + ))) + } + } + Ok(None) } } -impl Dsl for Bsp where S: Eval + Eval { +impl Dsl for Bsp +where S: Eval, A> + Eval, B> { fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(Some(match source.peek() { - Some(Value::Key("bsp/n")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/n: expected content 1")?; - let b: B = state.eval(source, ||"bsp/n: expected content 2")?; - Self::n(a, b) - }, - Some(Value::Key("bsp/s")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/s: expected content 1")?; - let b: B = state.eval(source, ||"bsp/s: expected content 2")?; - Self::s(a, b) - }, - Some(Value::Key("bsp/e")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/e: expected content 1")?; - let b: B = state.eval(source, ||"bsp/e: expected content 2")?; - Self::e(a, b) - }, - Some(Value::Key("bsp/w")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/w: expected content 1")?; - let b: B = state.eval(source, ||"bsp/w: expected content 2")?; - Self::w(a, b) - }, - Some(Value::Key("bsp/a")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/a: expected content 1")?; - let b: B = state.eval(source, ||"bsp/a: expected content 2")?; - Self::a(a, b) - }, - Some(Value::Key("bsp/b")) => { - let _ = source.next(); - let a: A = state.eval(source, ||"bsp/b: expected content 1")?; - let b: B = state.eval(source, ||"bsp/b: expected content 2")?; - Self::b(a, b) - }, - _ => return Ok(None), + Ok(Some(if let Ast::Exp(source) = source { + match source.next() { + Some(Value::Key("bsp/n")) => { + let a: A = state.eval(source.next(), ||"bsp/n: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/n: expected content 2")?; + Self::n(a, b) + }, + Some(Value::Key("bsp/s")) => { + let a: A = state.eval(source.next(), ||"bsp/s: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/s: expected content 2")?; + Self::s(a, b) + }, + Some(Value::Key("bsp/e")) => { + let a: A = state.eval(source.next(), ||"bsp/e: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/e: expected content 2")?; + Self::e(a, b) + }, + Some(Value::Key("bsp/w")) => { + let a: A = state.eval(source.next(), ||"bsp/w: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/w: expected content 2")?; + Self::w(a, b) + }, + Some(Value::Key("bsp/a")) => { + let a: A = state.eval(source.next(), ||"bsp/a: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/a: expected content 2")?; + Self::a(a, b) + }, + Some(Value::Key("bsp/b")) => { + let a: A = state.eval(source.next(), ||"bsp/b: expected content 1")?; + let b: B = state.eval(source.next(), ||"bsp/b: expected content 2")?; + Self::b(a, b) + }, + _ => return Ok(None), + } + } else { + return Ok(None) })) } } -impl Dsl for Align where S: Eval { +impl Dsl for Align where S: Eval, A> { fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(if let Some(Value::Key(key)) = source.peek() { - Some(match key { - "align/c" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/c: expected content")?; + Ok(Some(if let Ast::Exp(source) = source { + match source.next() { + Some(Value::Key("align/c")) => { + let content: A = state.eval(source.next(), ||"align/c: expected content")?; Self::c(content) }, - "align/x" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/x: expected content")?; + Some(Value::Key("align/x")) => { + let content: A = state.eval(source.next(), ||"align/x: expected content")?; Self::x(content) }, - "align/y" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/y: expected content")?; + Some(Value::Key("align/y")) => { + let content: A = state.eval(source.next(), ||"align/y: expected content")?; Self::y(content) }, - "align/n" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/n: expected content")?; + Some(Value::Key("align/n")) => { + let content: A = state.eval(source.next(), ||"align/n: expected content")?; Self::n(content) }, - "align/s" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/s: expected content")?; + Some(Value::Key("align/s")) => { + let content: A = state.eval(source.next(), ||"align/s: expected content")?; Self::s(content) }, - "align/e" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/e: expected content")?; + Some(Value::Key("align/e")) => { + let content: A = state.eval(source.next(), ||"align/e: expected content")?; Self::e(content) }, - "align/w" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/w: expected content")?; + Some(Value::Key("align/w")) => { + let content: A = state.eval(source.next(), ||"align/w: expected content")?; Self::w(content) }, - "align/nw" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/nw: expected content")?; + Some(Value::Key("align/nw")) => { + let content: A = state.eval(source.next(), ||"align/nw: expected content")?; Self::nw(content) }, - "align/ne" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/ne: expected content")?; + Some(Value::Key("align/ne")) => { + let content: A = state.eval(source.next(), ||"align/ne: expected content")?; Self::ne(content) }, - "align/sw" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/sw: expected content")?; + Some(Value::Key("align/sw")) => { + let content: A = state.eval(source.next(), ||"align/sw: expected content")?; Self::sw(content) }, - "align/se" => { - let _ = source.next(); - let content: A = state.eval(source, ||"align/se: expected content")?; + Some(Value::Key("align/se")) => { + let content: A = state.eval(source.next(), ||"align/se: expected content")?; Self::se(content) }, _ => return Ok(None), - }) + } } else { - None - }) + return Ok(None) + })) } } From d6e8be6ce5d2a907acd0be1cb7da9f13c8d713a0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 22:49:55 +0300 Subject: [PATCH 102/178] dsl gets the gordian treatment --- dsl/src/dsl_ast.rs | 84 ++++-------- dsl/src/dsl_cst.rs | 288 +---------------------------------------- dsl/src/dsl_display.rs | 39 ++++++ dsl/src/dsl_domain.rs | 22 +++- dsl/src/dsl_iter.rs | 96 ++++++++++++++ dsl/src/dsl_token.rs | 264 +++++++++++++++++++++++++++++++++++++ dsl/src/lib.rs | 3 + input/src/input_dsl.rs | 43 +++--- output/src/ops_dsl.rs | 157 ++++++++++------------ 9 files changed, 540 insertions(+), 456 deletions(-) create mode 100644 dsl/src/dsl_display.rs create mode 100644 dsl/src/dsl_iter.rs create mode 100644 dsl/src/dsl_token.rs diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 15c4374..010d3f6 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -1,70 +1,36 @@ use crate::*; use std::sync::Arc; -use std::fmt::{Debug, Display, Formatter}; +use std::borrow::Cow; -#[derive(Clone, Default, Debug)] -pub enum Ast { - #[default] Nil, - Err(DslError), - Num(usize), - Sym(Arc), - Key(Arc), - Str(Arc), - Exp(Arc>), -} +/// Owns its values, and has no metadata. +pub type AstToken = Token; -/// Emits tokens. -pub trait AstIter: Debug { - fn peek (&self) -> Option; - fn next (&mut self) -> Option; - fn rest (self) -> Option>; -} +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct AstMeta; -/// A [Cst] can be used as an [Ast]. -impl<'source: 'static> AstIter for Cst<'source> { - fn peek (&self) -> Option { - Cst::peek(self).map(|token|token.value.into()) - } - fn next (&mut self) -> Option { - Iterator::next(self).map(|token|token.value.into()) - } - fn rest (self) -> Option> { - self.peek().is_some().then(||Box::new(self) as Box) - } -} - -impl std::fmt::Display for Ast { - fn fmt (&self, out: &mut Formatter) -> Result<(), std::fmt::Error> { - use Ast::*; - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(e) => format!("{e:?}"), - }) - } -} - -impl<'source: 'static> From> for Ast { - fn from (other: CstValue<'source>) -> Self { - use CstValue::*; - match other { - Nil => Self::Nil, - Err(e) => Self::Err(e), - Num(u) => Self::Num(u), - Sym(s) => Self::Sym(s.into()), - Key(s) => Self::Key(s.into()), - Str(s) => Self::Str(s.into()), - Exp(_, s) => Self::Exp(Arc::new(s.into())), +pub type AstValue = Value, AstExp>; +impl<'source> From> for AstValue { + fn from (value: CstValue<'source>) -> Self { + use Value::*; + match value { + 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(x) => Exp(x.into()), } } } -impl<'source: 'static> Into> for Cst<'source> { - fn into (self) -> Box { - Box::new(self) +#[derive(Clone, Default, PartialEq)] +pub struct AstExp(pub Vec); +impl<'source> From> for AstExp { + fn from (exp: CstExp<'source>) -> AstExp { + AstExp(exp.words.map(|token|Token( + AstValue::from(token.value()), + AstMeta, + )).collect()) } } diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs index a45e77a..61b9352 100644 --- a/dsl/src/dsl_cst.rs +++ b/dsl/src/dsl_cst.rs @@ -1,291 +1,17 @@ use crate::*; -/// Provides a native [Iterator] API over [CstConstIter], -/// emitting [CstToken] items. -/// -/// [Cst::next] returns just the [CstToken] and mutates `self`, -/// instead of returning an updated version of the struct as [CstConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct Cst<'a>(pub CstConstIter<'a>); +/// Keeps the reference to the source slice. +pub type CstToken<'source> = Token, CstMeta<'source>>; -/// Owns a reference to the source text. -/// [CstConstIter::next] emits subsequent pairs of: -/// * a [CstToken] and -/// * the source text remaining -/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'a>(pub &'a str); - -/// A CST token, with reference to the source slice. -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstToken<'source> { +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { pub source: &'source str, pub start: usize, pub length: usize, - pub value: CstValue<'source>, } -/// The meaning of a CST token. Strip the source from this to get an [AstValue]. -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub enum CstValue<'source> { - #[default] Nil, - Err(DslError), - Num(usize), - Sym(&'source str), - Key(&'source str), - Str(&'source str), - Exp(usize, Cst<'source>), -} +pub type CstValue<'source> = Value<&'source str, CstExp<'source>>; -impl<'a> Cst<'a> { - pub const fn new (source: &'a str) -> Self { - Self(CstConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'a> Iterator for Cst<'a> { - type Item = CstToken<'a>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{self.0 = rest; item}) - } -} - -impl<'a> From<&'a str> for Cst<'a> { - fn from (source: &'a str) -> Self{ - Self(CstConstIter(source)) - } -} - -impl<'a> From> for Cst<'a> { - fn from (source: CstConstIter<'a>) -> Self{ - Self(source) - } -} - -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -const_iter!(<'a>|self: CstConstIter<'a>| => CstToken<'a> => self.next_mut().map(|(result, _)|result)); - -impl<'a> From<&'a str> for CstConstIter<'a> { - fn from (source: &'a str) -> Self{ - Self::new(source) - } -} - -impl<'a> CstConstIter<'a> { - pub const fn new (source: &'a str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(CstToken<'a>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(CstToken<'a>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'a> (source: &'a str) -> Option> { - use CstValue::*; - let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - CstToken::new(source, start, 1, Exp(1, Cst::new(str_range(source, start, start + 1)))), - '"' => - CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstToken::new(source, start, 1, match to_digit(c) { - Ok(c) => CstValue::Num(c), - Result::Err(e) => CstValue::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} - -impl<'source> CstToken<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: CstValue<'source> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: CstValue<'source>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> CstValue { - self.value - } - pub const fn error (self, error: DslError) -> Self { - Self { value: CstValue::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 { - use CstValue::*; - match to_digit(c) { - Ok(n) => Self { value: Num(10*m+n), ..self.grow() }, - Result::Err(e) => Self { value: Err(e), ..self.grow() }, - } - } - pub const fn grow_key (self) -> Self { - use CstValue::*; - let token = self.grow(); - token.with_value(Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - use CstValue::*; - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - use CstValue::*; - let token = self.grow(); - token.with_value(Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - use CstValue::*; - let token = self.grow(); - if let Exp(depth, _) = token.value { - token.with_value(Exp(depth, Cst::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let CstValue::Exp(depth, source) = token.value { - token.with_value(CstValue::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let CstValue::Exp(depth, source) = token.value { - if depth > 0 { - token.with_value(CstValue::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -impl<'source> std::fmt::Display for CstValue<'source> { - fn fmt (&self, out: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - use CstValue::*; - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) - } +#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstExp<'source> { + pub depth: usize, + pub words: SourceIter<'source> } diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs new file mode 100644 index 0000000..a986664 --- /dev/null +++ b/dsl/src/dsl_display.rs @@ -0,0 +1,39 @@ +use crate::*; + +use std::fmt::{Debug, Display, Formatter, Error as FormatError}; + +impl Display for AstValue { + fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { + use Value::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(e) => format!("{e:?}"), + }) + } +} + +impl Debug for AstExp { + fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { + todo!() + } +} + +impl<'source> Display for CstValue<'source> { + fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { + use Value::*; + write!(out, "{}", match self { + Nil => String::new(), + Err(e) => format!("[error: {e}]"), + Num(n) => format!("{n}"), + Sym(s) => format!("{s}"), + Key(s) => format!("{s}"), + Str(s) => format!("{s}"), + Exp(e) => format!("{e:?}"), + }) + } +} diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs index 88af556..8289ff8 100644 --- a/dsl/src/dsl_domain.rs +++ b/dsl/src/dsl_domain.rs @@ -13,13 +13,25 @@ pub trait Eval { } } +//impl, I, O> Eval for &S { + //fn try_eval (&self, input: I) -> Perhaps { + //(*self).try_eval(input) + //} +//} + +//impl, I: Ast, O: Dsl> Eval for S { + //fn try_eval (&self, input: I) -> Perhaps { + //Dsl::try_provide(self, input) + //} +//} + /// May construct [Self] from token stream. pub trait Dsl: Sized { - fn try_provide (state: State, source: Ast) -> Perhaps; + fn try_provide (state: &State, source: impl DslValue) -> Perhaps; fn provide >, F: Fn()->E> ( - state: State, source: Ast, error: F + state: &State, source: impl DslValue, error: F ) -> Usually { - let next = source.clone(); + let next = format!("{source:?}"); if let Some(value) = Self::try_provide(state, source)? { Ok(value) } else { @@ -28,10 +40,6 @@ pub trait Dsl: Sized { } } -impl, S> Eval for S { - fn try_eval (&self, input: Ast) -> Perhaps { todo!() } -} - //pub trait Give<'state, 'source, Type> { ///// Implement this to be able to [Give] [Type] from the [Cst]. ///// Advance the stream if returning `Ok>`. diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs new file mode 100644 index 0000000..9b5cc24 --- /dev/null +++ b/dsl/src/dsl_iter.rs @@ -0,0 +1,96 @@ +use crate::*; + +pub trait DslIter { + type Token: DslToken; + fn peek (&self) -> Option<::Value>; + fn next (&mut self) -> Option<::Value>; + fn rest (&self) -> Option<&[::Value]>; +} + +/// Provides a native [Iterator] API over [SourceConstIter], +/// emitting [CstToken] items. +/// +/// [Cst::next] returns just the [CstToken] and mutates `self`, +/// instead of returning an updated version of the struct as [SourceConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceIter<'a>(pub SourceConstIter<'a>); + +impl<'a> SourceIter<'a> { + pub const fn new (source: &'a str) -> Self { + Self(SourceConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} + +impl<'a> Iterator for SourceIter<'a> { + type Item = CstToken<'a>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{self.0 = rest; item}) + } +} + +impl<'a> From<&'a str> for SourceIter<'a> { + fn from (source: &'a str) -> Self{ + Self(SourceConstIter(source)) + } +} + +/// Implement the const iterator pattern. +#[macro_export] macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } + } +} + +/// Owns a reference to the source text. +/// [SourceConstIter::next] emits subsequent pairs of: +/// * a [CstToken] and +/// * the source text remaining +/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceConstIter<'a>(pub &'a str); + +impl<'a> From> for SourceIter<'a> { + fn from (source: SourceConstIter<'a>) -> Self{ + Self(source) + } +} + +impl<'a> From<&'a str> for SourceConstIter<'a> { + fn from (source: &'a str) -> Self{ + Self::new(source) + } +} + +impl<'a> SourceConstIter<'a> { + pub const fn new (source: &'a str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(CstToken<'a>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(CstToken<'a>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + +const_iter!(<'a>|self: SourceConstIter<'a>| => CstToken<'a> => self.next_mut().map(|(result, _)|result)); diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs new file mode 100644 index 0000000..6a132a9 --- /dev/null +++ b/dsl/src/dsl_token.rs @@ -0,0 +1,264 @@ +use crate::*; + +#[derive(PartialEq, Clone, Default, Debug)] +pub enum Value< + S: PartialEq + Clone + Default + Debug, + X: PartialEq + Clone + Default + Debug, +> { + #[default] Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(X), +} + +pub trait DslValue: PartialEq + Clone + Default + Debug { + type Err: PartialEq + Clone + Debug; + type Num: PartialEq + Copy + Clone + Default + Debug; + type Str: PartialEq + Clone + Default + Debug; + type Exp: PartialEq + Clone + Default + Debug; + fn nil (&self) -> bool; + fn err (&self) -> Option<&Self::Err>; + fn num (&self) -> Option; + fn sym (&self) -> Option<&Self::Str>; + fn key (&self) -> Option<&Self::Str>; + fn str (&self) -> Option<&Self::Str>; + fn exp (&self) -> Option<&Self::Exp>; + fn exp_head (&self) -> Option<&Self> { None } // TODO + fn exp_tail (&self) -> Option<&[Self]> { None } // TODO +} + +impl< + Str: PartialEq + Clone + Default + Debug, + Exp: PartialEq + Clone + Default + Debug, +> DslValue for Value { + type Err = DslError; + type Num = usize; + type Str = Str; + type Exp = Exp; + fn nil (&self) -> bool { + matches!(self, Self::Nil) + } + fn err (&self) -> Option<&DslError> { + if let Self::Err(e) = self { Some(e) } else { None } + } + fn num (&self) -> Option { + if let Self::Num(n) = self { Some(*n) } else { None } + } + fn sym (&self) -> Option<&Str> { + if let Self::Sym(s) = self { Some(s) } else { None } + } + fn key (&self) -> Option<&Str> { + if let Self::Key(k) = self { Some(k) } else { None } + } + fn str (&self) -> Option<&Str> { + if let Self::Str(s) = self { Some(s) } else { None } + } + fn exp (&self) -> Option<&Exp> { + if let Self::Exp(x) = self { Some(x) } else { None } + } +} + + +#[derive(PartialEq, Clone, Default, Debug)] +pub struct Token< + V: PartialEq + Clone + Default + Debug, + M: PartialEq + Clone + Default + Debug, +>(pub V, pub M); + +pub trait DslToken: PartialEq + Clone + Default + Debug { + type Value: DslValue; + type Meta: Clone + Default + Debug; + fn value (&self) -> &Self::Value; + fn meta (&self) -> &Self::Meta; +} + +impl DslToken for Token { + type Value = V; + type Meta = M; + fn value (&self) -> &Self::Value { + &self.0 + } + fn meta (&self) -> &Self::Meta { + &self.1 + } +} + +impl<'source> CstToken<'source> { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: CstValue<'source> + ) -> Self { + Self(value, CstMeta { source, start, length }) + } + pub const fn end (&self) -> usize { + self.1.start.saturating_add(self.1.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.1.source) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.1.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.1.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: CstValue<'source>) -> Self { + Self(value, self.1) + } + pub const fn value (&self) -> CstValue<'source> { + self.0 + } + pub const fn error (self, error: DslError) -> Self { + Self(Value::Err(error), self.1) + } + pub const fn grow (self) -> Self { + Self(self.0, CstMeta { length: self.1.length.saturating_add(1), ..self.1 }) + } + pub const fn grow_num (self, m: usize, c: char) -> Self { + use Value::*; + match to_digit(c) { + Result::Ok(n) => Self(Num(10*m+n), self.grow().1), + Result::Err(e) => Self(Err(e), self.grow().1), + } + } + pub const fn grow_key (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Key(token.slice_source(self.1.source))) + } + pub const fn grow_sym (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.1.source))) + } + pub const fn grow_str (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Str(token.slice_source(self.1.source))) + } + pub const fn grow_exp (self) -> Self { + use Value::*; + let token = self.grow(); + if let Exp(depth, _) = token.value() { + token.with_value(Exp(depth, Cst::new(token.slice_source_exp(self.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + token.with_value(Value::Exp(depth.saturating_add(1), source)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value { + if depth > 0 { + token.with_value(Value::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) +} + +/// Static iteration helper. +#[macro_export] macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +pub const fn peek_src <'a> (source: &'a str) -> Option> { + use Value::*; + let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); + iterate!(char_indices(source) => (start, c) => token = match token.value() { + Err(_) => return Some(token), + Nil => match c { + ' '|'\n'|'\r'|'\t' => + token.grow(), + '(' => + CstToken::new(source, start, 1, Exp(1, Cst::new(str_range(source, start, start + 1)))), + '"' => + CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), + '0'..='9' => + CstToken::new(source, start, 1, match to_digit(c) { + Ok(c) => Value::Num(c), + Result::Err(e) => Value::Err(e) + }), + _ => token.error(Unexpected(c)) + }, + Str(_) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, + Num(n) => match c { + '0'..='9' => token.grow_num(n, c), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Sym(_) => match c { + 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Key(_) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Exp(depth, _) => match depth { + 0 => return Some(token.grow_exp()), + _ => match c { + ')' => token.grow_out(), + '(' => token.grow_in(), + _ => token.grow_exp(), + } + }, + }); + match token.value() { + Nil => None, + _ => Some(token), + } +} + +//impl, X1, X2: From> From> for Value { + //fn from (other: Value) -> Self { + //use Value::*; + //match other { + //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(x) => Exp(x.into()), + //} + //} +//} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 0cd66c8..8fc06e4 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -46,8 +46,11 @@ pub(crate) use self::DslError::*; mod dsl_ast; pub use self::dsl_ast::*; mod dsl_cst; pub use self::dsl_cst::*; +mod dsl_display; pub use self::dsl_display::*; mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; +mod dsl_iter; pub use self::dsl_iter::*; +mod dsl_token; pub use self::dsl_token::*; #[cfg(test)] mod test_token_iter { use crate::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index c6d26e1..7bd3605 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,40 +1,47 @@ use crate::*; use std::marker::PhantomData; use std::fmt::Debug; -/// List of input layers with optional conditional filters. -#[derive(Default, Debug)] pub struct InputLayers(Vec>); -#[derive(Default, Debug)] pub struct InputLayer{ - __: PhantomData, - condition: Option, - binding: Ast, -} -impl InputLayers { - pub fn new (layer: Ast) -> Self { + +#[derive(Default, Debug)] pub struct InputLayers(Vec); + +#[derive(Default, Debug)] pub struct InputLayer(Option>, Cst<'static>); + +impl InputLayers { + pub fn new (layer: Cst<'static>) -> Self { Self(vec![]).layer(layer) } - pub fn layer (mut self, layer: Ast) -> Self { + pub fn layer (mut self, layer: Cst<'static>) -> Self { self.add_layer(layer); self } - pub fn layer_if (mut self, condition: Ast, layer: Ast) -> Self { + pub fn layer_if (mut self, condition: Cst<'static>, layer: Cst<'static>) -> Self { self.add_layer_if(Some(condition), layer); self } - pub fn add_layer (&mut self, layer: Ast) -> &mut Self { + pub fn add_layer (&mut self, layer: Cst<'static>) -> &mut Self { self.add_layer_if(None, layer.into()); self } - pub fn add_layer_if (&mut self, condition: Option, binding: Ast) -> &mut Self { + pub fn add_layer_if (&mut self, condition: Option>, binding: Cst<'static>) -> &mut Self { self.0.push(InputLayer { condition, binding, __: Default::default() }); self } + pub fn handle , I, O: Command> ( + &self, state: &mut S, input: I + ) -> Perhaps { + InputHandle(state, self.0.as_slice()).try_eval(input) + } } -impl + Eval, C: Command, I: Debug + Eval> Eval<(S, I), C> for InputLayers { - fn try_eval (&self, (state, input): (S, I)) -> Perhaps { - for InputLayer { condition, binding, .. } in self.0.iter() { + +pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); + +impl<'a, S: Eval<&'a dyn Ast, bool>, I, O: Command> Eval for InputHandle<'a, S> { + fn try_eval (&self, input: I) -> Perhaps { + let Self(state, layers) = self; + for InputLayer(condition, binding) in layers.iter() { let mut matches = true; if let Some(condition) = condition { - matches = state.eval(condition.clone(), ||"input: no condition")?; + matches = state.eval(*condition, ||"input: no condition")?; } if matches { - if let Ast::Exp(e) = binding { + if let AstValue::Exp(e) = binding.peek() { if let Some(ast) = e.peek() { if input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? && let Some(command) = state.try_eval(ast)? { diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index e86a634..2dd72fb 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -1,12 +1,13 @@ use crate::*; impl Dsl for When where S: Eval + Eval { - fn try_provide (state: S, source: Ast) -> Perhaps { - if let Ast::Exp(source) = source { - if let Some(Ast::Key(id)) = source.next() && *id == *"when" { + fn try_provide (state: S, source: impl Ast) -> Perhaps { + if let Ast::Exp(exp) = source { + let mut iter = source.clone(); + if let Some(Ast::Key(id)) = iter.next() && *id == *"when" { return Ok(Some(Self( - state.eval(source.next().unwrap(), ||"when: expected condition")?, - state.eval(source.next().unwrap(), ||"when: expected content")?, + state.eval(iter.next().unwrap(), ||"when: expected condition")?, + state.eval(iter.next().unwrap(), ||"when: expected content")?, ))) } } @@ -15,13 +16,14 @@ impl Dsl for When where S: Eval + Eval { } impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: S, mut source: Ast) -> Perhaps { - if let Ast::Exp(mut source) = source { - if let Some(Ast::Key(id)) = source.next() && *id == *"either" { + fn try_provide (state: S, source: impl Ast) -> Perhaps { + if let Ast::Exp(source) = source { + let mut iter = source.clone(); + if let Some(Ast::Key(id)) = iter.next() && *id == *"either" { return Ok(Some(Self( - state.eval(source.next().unwrap(), ||"either: expected condition")?, - state.eval(source.next().unwrap(), ||"either: expected content 1")?, - state.eval(source.next().unwrap(), ||"either: expected content 2")?, + state.eval(iter.next().unwrap(), ||"either: expected condition")?, + state.eval(iter.next().unwrap(), ||"either: expected content 1")?, + state.eval(iter.next().unwrap(), ||"either: expected content 2")?, ))) } } @@ -29,41 +31,35 @@ impl Dsl for Either where S: Eval + Eval + } } -impl Dsl for Bsp -where S: Eval, A> + Eval, B> { - fn try_provide (state: S, source: Ast) -> Perhaps { +impl Dsl for Bsp where S: Eval, A> + Eval, B> { + fn try_provide (state: S, source: impl Ast) -> Perhaps { Ok(Some(if let Ast::Exp(source) = source { - match source.next() { - Some(Value::Key("bsp/n")) => { - let a: A = state.eval(source.next(), ||"bsp/n: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/n: expected content 2")?; - Self::n(a, b) - }, - Some(Value::Key("bsp/s")) => { - let a: A = state.eval(source.next(), ||"bsp/s: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/s: expected content 2")?; - Self::s(a, b) - }, - Some(Value::Key("bsp/e")) => { - let a: A = state.eval(source.next(), ||"bsp/e: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/e: expected content 2")?; - Self::e(a, b) - }, - Some(Value::Key("bsp/w")) => { - let a: A = state.eval(source.next(), ||"bsp/w: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/w: expected content 2")?; - Self::w(a, b) - }, - Some(Value::Key("bsp/a")) => { - let a: A = state.eval(source.next(), ||"bsp/a: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/a: expected content 2")?; - Self::a(a, b) - }, - Some(Value::Key("bsp/b")) => { - let a: A = state.eval(source.next(), ||"bsp/b: expected content 1")?; - let b: B = state.eval(source.next(), ||"bsp/b: expected content 2")?; - Self::b(a, b) - }, + let mut iter = source.clone(); + match iter.next() { + Some(Value::Key("bsp/n")) => Self::n( + state.eval(iter.next(), ||"bsp/n: expected content 1")?, + state.eval(iter.next(), ||"bsp/n: expected content 2")?, + ), + Some(Value::Key("bsp/s")) => Self::s( + state.eval(iter.next(), ||"bsp/s: expected content 1")?, + state.eval(iter.next(), ||"bsp/s: expected content 2")?, + ), + Some(Value::Key("bsp/e")) => Self::e( + state.eval(iter.next(), ||"bsp/e: expected content 1")?, + state.eval(iter.next(), ||"bsp/e: expected content 2")?, + ), + Some(Value::Key("bsp/w")) => Self::w( + state.eval(iter.next(), ||"bsp/w: expected content 1")?, + state.eval(iter.next(), ||"bsp/w: expected content 2")?, + ), + Some(Value::Key("bsp/a")) => Self::a( + state.eval(iter.next(), ||"bsp/a: expected content 1")?, + state.eval(iter.next(), ||"bsp/a: expected content 2")?, + ), + Some(Value::Key("bsp/b")) => Self::b( + state.eval(iter.next(), ||"bsp/b: expected content 1")?, + state.eval(iter.next(), ||"bsp/b: expected content 2")?, + ), _ => return Ok(None), } } else { @@ -73,53 +69,32 @@ where S: Eval, A> + Eval, B> { } impl Dsl for Align where S: Eval, A> { - fn try_provide (state: S, source: Ast) -> Perhaps { + fn try_provide (state: S, source: impl Ast) -> Perhaps { Ok(Some(if let Ast::Exp(source) = source { - match source.next() { - Some(Value::Key("align/c")) => { - let content: A = state.eval(source.next(), ||"align/c: expected content")?; - Self::c(content) - }, - Some(Value::Key("align/x")) => { - let content: A = state.eval(source.next(), ||"align/x: expected content")?; - Self::x(content) - }, - Some(Value::Key("align/y")) => { - let content: A = state.eval(source.next(), ||"align/y: expected content")?; - Self::y(content) - }, - Some(Value::Key("align/n")) => { - let content: A = state.eval(source.next(), ||"align/n: expected content")?; - Self::n(content) - }, - Some(Value::Key("align/s")) => { - let content: A = state.eval(source.next(), ||"align/s: expected content")?; - Self::s(content) - }, - Some(Value::Key("align/e")) => { - let content: A = state.eval(source.next(), ||"align/e: expected content")?; - Self::e(content) - }, - Some(Value::Key("align/w")) => { - let content: A = state.eval(source.next(), ||"align/w: expected content")?; - Self::w(content) - }, - Some(Value::Key("align/nw")) => { - let content: A = state.eval(source.next(), ||"align/nw: expected content")?; - Self::nw(content) - }, - Some(Value::Key("align/ne")) => { - let content: A = state.eval(source.next(), ||"align/ne: expected content")?; - Self::ne(content) - }, - Some(Value::Key("align/sw")) => { - let content: A = state.eval(source.next(), ||"align/sw: expected content")?; - Self::sw(content) - }, - Some(Value::Key("align/se")) => { - let content: A = state.eval(source.next(), ||"align/se: expected content")?; - Self::se(content) - }, + let mut iter = source.clone(); + match iter.next() { + Some(Value::Key("align/c")) => + Self::c(state.eval(iter.next(), ||"align/c: expected content")?), + Some(Value::Key("align/x")) => + Self::x(state.eval(iter.next(), ||"align/x: expected content")?), + Some(Value::Key("align/y")) => + Self::y(state.eval(iter.next(), ||"align/y: expected content")?), + Some(Value::Key("align/n")) => + Self::n(state.eval(iter.next(), ||"align/n: expected content")?), + Some(Value::Key("align/s")) => + Self::s(state.eval(iter.next(), ||"align/s: expected content")?), + Some(Value::Key("align/e")) => + Self::e(state.eval(iter.next(), ||"align/e: expected content")?), + Some(Value::Key("align/w")) => + Self::w(state.eval(iter.next(), ||"align/w: expected content")?), + Some(Value::Key("align/nw")) => + Self::nw(state.eval(iter.next(), ||"align/nw: expected content")?), + Some(Value::Key("align/ne")) => + Self::ne(state.eval(iter.next(), ||"align/ne: expected content")?), + Some(Value::Key("align/sw")) => + Self::sw(state.eval(iter.next(), ||"align/sw: expected content")?), + Some(Value::Key("align/se")) => + Self::se(state.eval(iter.next(), ||"align/se: expected content")?), _ => return Ok(None), } } else { From f77139c8fdda9dcff048ed2613db74ac3156c154 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 23:21:38 +0300 Subject: [PATCH 103/178] wip: wee --- dsl/src/dsl_ast.rs | 24 ++++++++--------- dsl/src/dsl_cst.rs | 10 +++----- dsl/src/dsl_display.rs | 13 ++-------- dsl/src/dsl_iter.rs | 58 +++++++++++++++++++++++++++--------------- dsl/src/dsl_token.rs | 22 ++++++++++------ 5 files changed, 68 insertions(+), 59 deletions(-) diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 010d3f6..0936cde 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -3,12 +3,13 @@ use std::sync::Arc; use std::borrow::Cow; /// Owns its values, and has no metadata. -pub type AstToken = Token; +#[derive(PartialEq, Clone, Default, Debug)] +pub struct AstToken(pub AstValue, pub AstMeta); #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct AstMeta; -pub type AstValue = Value, AstExp>; +pub type AstValue = Value, Vec>; impl<'source> From> for AstValue { fn from (value: CstValue<'source>) -> Self { use Value::*; @@ -19,18 +20,15 @@ impl<'source> From> for AstValue { Sym(s) => Sym(s.into()), Key(s) => Key(s.into()), Str(s) => Str(s.into()), - Exp(x) => Exp(x.into()), + Exp(d, x) => Exp(d, x.into()), } } } -#[derive(Clone, Default, PartialEq)] -pub struct AstExp(pub Vec); -impl<'source> From> for AstExp { - fn from (exp: CstExp<'source>) -> AstExp { - AstExp(exp.words.map(|token|Token( - AstValue::from(token.value()), - AstMeta, - )).collect()) - } -} +pub trait AstIter: DslIter {} + +//impl<'source> From> for AstExp { + //fn from (exp: CstExp<'source>) -> AstExp { + //AstExp(exp.words.map(|token|Token(AstValue::from(token.value()), AstMeta,)).collect()) + //} +//} diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs index 61b9352..1c896b5 100644 --- a/dsl/src/dsl_cst.rs +++ b/dsl/src/dsl_cst.rs @@ -1,7 +1,8 @@ use crate::*; /// Keeps the reference to the source slice. -pub type CstToken<'source> = Token, CstMeta<'source>>; +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { pub source: &'source str, @@ -9,9 +10,4 @@ pub type CstToken<'source> = Token, CstMeta<'source>>; pub length: usize, } -pub type CstValue<'source> = Value<&'source str, CstExp<'source>>; - -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstExp<'source> { - pub depth: usize, - pub words: SourceIter<'source> -} +pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs index a986664..8a10193 100644 --- a/dsl/src/dsl_display.rs +++ b/dsl/src/dsl_display.rs @@ -1,7 +1,5 @@ use crate::*; - use std::fmt::{Debug, Display, Formatter, Error as FormatError}; - impl Display for AstValue { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { use Value::*; @@ -12,17 +10,10 @@ impl Display for AstValue { Sym(s) => format!("{s}"), Key(s) => format!("{s}"), Str(s) => format!("{s}"), - Exp(e) => format!("{e:?}"), + Exp(_, e) => format!("{e:?}"), }) } } - -impl Debug for AstExp { - fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { - todo!() - } -} - impl<'source> Display for CstValue<'source> { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { use Value::*; @@ -33,7 +24,7 @@ impl<'source> Display for CstValue<'source> { Sym(s) => format!("{s}"), Key(s) => format!("{s}"), Str(s) => format!("{s}"), - Exp(e) => format!("{e:?}"), + Exp(_, e) => format!("{e:?}"), }) } } diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index 9b5cc24..6b12682 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -13,30 +13,48 @@ pub trait DslIter { /// [Cst::next] returns just the [CstToken] and mutates `self`, /// instead of returning an updated version of the struct as [SourceConstIter::next] does. #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'a>(pub SourceConstIter<'a>); +pub struct SourceIter<'source>(pub SourceConstIter<'source>); -impl<'a> SourceIter<'a> { - pub const fn new (source: &'a str) -> Self { +impl<'source> SourceIter<'source> { + pub const fn new (source: &'source str) -> Self { Self(SourceConstIter::new(source)) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { self.0.peek() } } -impl<'a> Iterator for SourceIter<'a> { - type Item = CstToken<'a>; - fn next (&mut self) -> Option> { +impl<'source> Iterator for SourceIter<'source> { + type Item = CstToken<'source>; + fn next (&mut self) -> Option> { self.0.next().map(|(item, rest)|{self.0 = rest; item}) } } -impl<'a> From<&'a str> for SourceIter<'a> { - fn from (source: &'a str) -> Self{ +impl<'source> From<&'source str> for SourceIter<'source> { + fn from (source: &'source str) -> Self{ Self(SourceConstIter(source)) } } +impl<'source> Into>> for SourceIter<'source> { + fn into (self) -> Vec> { + self.collect() + } +} + +impl<'source> Into> for SourceIter<'source> { + fn into (self) -> Vec { + self.map(Into::into).collect() + } +} + +impl<'source> From> for AstToken { + fn from (value: CstToken<'source>) -> Self { + Self(value.0.into(), AstMeta) + } +} + /// Implement the const iterator pattern. #[macro_export] macro_rules! const_iter { ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { @@ -58,34 +76,34 @@ impl<'a> From<&'a str> for SourceIter<'a> { /// * the source text remaining /// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceConstIter<'a>(pub &'a str); +pub struct SourceConstIter<'source>(pub &'source str); -impl<'a> From> for SourceIter<'a> { - fn from (source: SourceConstIter<'a>) -> Self{ +impl<'source> From> for SourceIter<'source> { + fn from (source: SourceConstIter<'source>) -> Self{ Self(source) } } -impl<'a> From<&'a str> for SourceConstIter<'a> { - fn from (source: &'a str) -> Self{ +impl<'source> From<&'source str> for SourceConstIter<'source> { + fn from (source: &'source str) -> Self{ Self::new(source) } } -impl<'a> SourceConstIter<'a> { - pub const fn new (source: &'a str) -> Self { +impl<'source> SourceConstIter<'source> { + pub const fn new (source: &'source str) -> Self { Self(source) } pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn next (mut self) -> Option<(CstToken<'a>, Self)> { + pub const fn next (mut self) -> Option<(CstToken<'source>, Self)> { Self::next_mut(&mut self) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { peek_src(self.0) } - pub const fn next_mut (&mut self) -> Option<(CstToken<'a>, Self)> { + pub const fn next_mut (&mut self) -> Option<(CstToken<'source>, Self)> { match self.peek() { Some(token) => Some((token, self.chomp(token.end()))), None => None @@ -93,4 +111,4 @@ impl<'a> SourceConstIter<'a> { } } -const_iter!(<'a>|self: SourceConstIter<'a>| => CstToken<'a> => self.next_mut().map(|(result, _)|result)); +const_iter!(<'source>|self: SourceConstIter<'source>| => CstToken<'source> => self.next_mut().map(|(result, _)|result)); diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs index 6a132a9..71d1997 100644 --- a/dsl/src/dsl_token.rs +++ b/dsl/src/dsl_token.rs @@ -5,9 +5,14 @@ pub enum Value< S: PartialEq + Clone + Default + Debug, X: PartialEq + Clone + Default + Debug, > { - #[default] Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(X), + #[default] Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(usize, X), } +impl< + S: Copy + PartialEq + Clone + Default + Debug, + X: Copy + PartialEq + Clone + Default + Debug, +> Copy for Value {} + pub trait DslValue: PartialEq + Clone + Default + Debug { type Err: PartialEq + Clone + Debug; type Num: PartialEq + Copy + Clone + Default + Debug; @@ -20,8 +25,9 @@ pub trait DslValue: PartialEq + Clone + Default + Debug { fn key (&self) -> Option<&Self::Str>; fn str (&self) -> Option<&Self::Str>; fn exp (&self) -> Option<&Self::Exp>; - fn exp_head (&self) -> Option<&Self> { None } // TODO - fn exp_tail (&self) -> Option<&[Self]> { None } // TODO + fn exp_depth (&self) -> Option { None } // TODO + fn exp_head (&self) -> Option<&Self> { None } // TODO + fn exp_tail (&self) -> Option<&[Self]> { None } // TODO } impl< @@ -51,7 +57,7 @@ impl< if let Self::Str(s) = self { Some(s) } else { None } } fn exp (&self) -> Option<&Exp> { - if let Self::Exp(x) = self { Some(x) } else { None } + if let Self::Exp(_, x) = self { Some(x) } else { None } } } @@ -136,14 +142,14 @@ impl<'source> CstToken<'source> { use Value::*; let token = self.grow(); if let Exp(depth, _) = token.value() { - token.with_value(Exp(depth, Cst::new(token.slice_source_exp(self.source)))) + token.with_value(Exp(depth, SourceIter::new(token.slice_source_exp(self.1.source)))) } else { unreachable!() } } pub const fn grow_in (self) -> Self { let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { + if let Value::Exp(depth, source) = token.value() { token.with_value(Value::Exp(depth.saturating_add(1), source)) } else { unreachable!() @@ -151,7 +157,7 @@ impl<'source> CstToken<'source> { } pub const fn grow_out (self) -> Self { let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value { + if let Value::Exp(depth, source) = token.value() { if depth > 0 { token.with_value(Value::Exp(depth - 1, source)) } else { @@ -200,7 +206,7 @@ pub const fn peek_src <'a> (source: &'a str) -> Option> { ' '|'\n'|'\r'|'\t' => token.grow(), '(' => - CstToken::new(source, start, 1, Exp(1, Cst::new(str_range(source, start, start + 1)))), + CstToken::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), '"' => CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), ':'|'@' => From e9d4c7e0bcd203c9635dacb7b8df0841a0624c9f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 23:32:59 +0300 Subject: [PATCH 104/178] AstToken -> Ast <- AstValue --- dsl/src/dsl_ast.rs | 30 ++++++++++++++---------------- dsl/src/dsl_display.rs | 2 +- dsl/src/dsl_iter.rs | 10 ++-------- input/src/input_dsl.rs | 20 +++++++++----------- output/src/ops_dsl.rs | 8 ++++---- 5 files changed, 30 insertions(+), 40 deletions(-) diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 0936cde..866e15f 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -2,15 +2,21 @@ use crate::*; use std::sync::Arc; use std::borrow::Cow; -/// Owns its values, and has no metadata. -#[derive(PartialEq, Clone, Default, Debug)] -pub struct AstToken(pub AstValue, pub AstMeta); - #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct AstMeta; - -pub type AstValue = Value, Vec>; -impl<'source> From> for AstValue { +pub struct AstIter; +impl<'source> From> for AstIter { + fn from (source: SourceIter<'source>) -> Self { + Self // TODO + } +} +/// Owns its values, and has no metadata. +pub type Ast = Value, AstIter>; +impl<'source> From> for Ast { + fn from (token: CstToken<'source>) -> Self { + token.value().into() + } +} +impl<'source> From> for Ast { fn from (value: CstValue<'source>) -> Self { use Value::*; match value { @@ -24,11 +30,3 @@ impl<'source> From> for AstValue { } } } - -pub trait AstIter: DslIter {} - -//impl<'source> From> for AstExp { - //fn from (exp: CstExp<'source>) -> AstExp { - //AstExp(exp.words.map(|token|Token(AstValue::from(token.value()), AstMeta,)).collect()) - //} -//} diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs index 8a10193..44def5d 100644 --- a/dsl/src/dsl_display.rs +++ b/dsl/src/dsl_display.rs @@ -1,6 +1,6 @@ use crate::*; use std::fmt::{Debug, Display, Formatter, Error as FormatError}; -impl Display for AstValue { +impl Display for Ast { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { use Value::*; write!(out, "{}", match self { diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index 6b12682..b532648 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -43,18 +43,12 @@ impl<'source> Into>> for SourceIter<'source> { } } -impl<'source> Into> for SourceIter<'source> { - fn into (self) -> Vec { +impl<'source> Into> for SourceIter<'source> { + fn into (self) -> Vec { self.map(Into::into).collect() } } -impl<'source> From> for AstToken { - fn from (value: CstToken<'source>) -> Self { - Self(value.0.into(), AstMeta) - } -} - /// Implement the const iterator pattern. #[macro_export] macro_rules! const_iter { ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 7bd3605..6f6b24b 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,35 +4,33 @@ use std::fmt::Debug; #[derive(Default, Debug)] pub struct InputLayers(Vec); -#[derive(Default, Debug)] pub struct InputLayer(Option>, Cst<'static>); +#[derive(Default, Debug)] pub struct InputLayer(Option, Ast); impl InputLayers { - pub fn new (layer: Cst<'static>) -> Self { + pub fn new (layer: Ast) -> Self { Self(vec![]).layer(layer) } - pub fn layer (mut self, layer: Cst<'static>) -> Self { + pub fn layer (mut self, layer: Ast) -> Self { self.add_layer(layer); self } - pub fn layer_if (mut self, condition: Cst<'static>, layer: Cst<'static>) -> Self { + pub fn layer_if (mut self, condition: Ast, layer: Ast) -> Self { self.add_layer_if(Some(condition), layer); self } - pub fn add_layer (&mut self, layer: Cst<'static>) -> &mut Self { + pub fn add_layer (&mut self, layer: Ast) -> &mut Self { self.add_layer_if(None, layer.into()); self } - pub fn add_layer_if (&mut self, condition: Option>, binding: Cst<'static>) -> &mut Self { - self.0.push(InputLayer { condition, binding, __: Default::default() }); + pub fn add_layer_if (&mut self, condition: Option, binding: Ast) -> &mut Self { + self.0.push(InputLayer(condition, binding)); self } - pub fn handle , I, O: Command> ( - &self, state: &mut S, input: I - ) -> Perhaps { + pub fn handle > (&self, state: &mut S, input: I) -> Perhaps { InputHandle(state, self.0.as_slice()).try_eval(input) } } pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); -impl<'a, S: Eval<&'a dyn Ast, bool>, I, O: Command> Eval for InputHandle<'a, S> { +impl<'a, S, I, O: Command> Eval for InputHandle<'a, S> { fn try_eval (&self, input: I) -> Perhaps { let Self(state, layers) = self; for InputLayer(condition, binding) in layers.iter() { diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index 2dd72fb..f14194b 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -1,7 +1,7 @@ use crate::*; impl Dsl for When where S: Eval + Eval { - fn try_provide (state: S, source: impl Ast) -> Perhaps { + fn try_provide (state: S, source: Ast) -> Perhaps { if let Ast::Exp(exp) = source { let mut iter = source.clone(); if let Some(Ast::Key(id)) = iter.next() && *id == *"when" { @@ -16,7 +16,7 @@ impl Dsl for When where S: Eval + Eval { } impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: S, source: impl Ast) -> Perhaps { + fn try_provide (state: S, source: Ast) -> Perhaps { if let Ast::Exp(source) = source { let mut iter = source.clone(); if let Some(Ast::Key(id)) = iter.next() && *id == *"either" { @@ -32,7 +32,7 @@ impl Dsl for Either where S: Eval + Eval + } impl Dsl for Bsp where S: Eval, A> + Eval, B> { - fn try_provide (state: S, source: impl Ast) -> Perhaps { + fn try_provide (state: S, source: Ast) -> Perhaps { Ok(Some(if let Ast::Exp(source) = source { let mut iter = source.clone(); match iter.next() { @@ -69,7 +69,7 @@ impl Dsl for Bsp where S: Eval, A> + Eval Dsl for Align where S: Eval, A> { - fn try_provide (state: S, source: impl Ast) -> Perhaps { + fn try_provide (state: S, source: Ast) -> Perhaps { Ok(Some(if let Ast::Exp(source) = source { let mut iter = source.clone(); match iter.next() { From 08a8dff93d446389136d07ad91c4d7eed0b6c6b2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 26 May 2025 23:39:02 +0300 Subject: [PATCH 105/178] wip: dsl: continuing to offload to Ast --- dsl/src/dsl_ast.rs | 24 ++++++++++++------------ dsl/src/dsl_cst.rs | 9 ++++----- dsl/src/dsl_display.rs | 2 +- input/src/input_dsl.rs | 4 ++-- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 866e15f..fca3fd6 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -1,16 +1,8 @@ use crate::*; use std::sync::Arc; use std::borrow::Cow; - -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct AstIter; -impl<'source> From> for AstIter { - fn from (source: SourceIter<'source>) -> Self { - Self // TODO - } -} -/// Owns its values, and has no metadata. -pub type Ast = Value, AstIter>; +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(pub Value, AstIter>); impl<'source> From> for Ast { fn from (token: CstToken<'source>) -> Self { token.value().into() @@ -19,7 +11,7 @@ impl<'source> From> for Ast { impl<'source> From> for Ast { fn from (value: CstValue<'source>) -> Self { use Value::*; - match value { + Self(match value { Nil => Nil, Err(e) => Err(e), Num(u) => Num(u), @@ -27,6 +19,14 @@ impl<'source> From> for Ast { Key(s) => Key(s.into()), Str(s) => Str(s.into()), Exp(d, x) => Exp(d, x.into()), - } + }) + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct AstIter; // TODO +impl<'source> From> for AstIter { + fn from (source: SourceIter<'source>) -> Self { + Self // TODO } } diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs index 1c896b5..1af6f8f 100644 --- a/dsl/src/dsl_cst.rs +++ b/dsl/src/dsl_cst.rs @@ -1,13 +1,12 @@ use crate::*; - -/// Keeps the reference to the source slice. +/// CST stores strings as source references and expressions as new [SourceIter] instances. +pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; +/// Token sharing memory with source reference. #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); - +/// Reference to the source slice. #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { pub source: &'source str, pub start: usize, pub length: usize, } - -pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs index 44def5d..aaae8c7 100644 --- a/dsl/src/dsl_display.rs +++ b/dsl/src/dsl_display.rs @@ -3,7 +3,7 @@ use std::fmt::{Debug, Display, Formatter, Error as FormatError}; impl Display for Ast { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { use Value::*; - write!(out, "{}", match self { + write!(out, "{}", match &self.0 { Nil => String::new(), Err(e) => format!("[error: {e}]"), Num(n) => format!("{n}"), diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 6f6b24b..bff457d 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -23,14 +23,14 @@ impl InputLayers { self.0.push(InputLayer(condition, binding)); self } - pub fn handle > (&self, state: &mut S, input: I) -> Perhaps { + pub fn handle , I, O: Command> (&self, state: &mut S, input: I) -> Perhaps { InputHandle(state, self.0.as_slice()).try_eval(input) } } pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); -impl<'a, S, I, O: Command> Eval for InputHandle<'a, S> { +impl<'a, S: Eval, I, O: Command> Eval for InputHandle<'a, S> { fn try_eval (&self, input: I) -> Perhaps { let Self(state, layers) = self; for InputLayer(condition, binding) in layers.iter() { From cb47c4d0ff6d68f55f6ff716933914b93bbcb0a4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 27 May 2025 00:53:06 +0300 Subject: [PATCH 106/178] dsl goes fat --- dsl/src/dsl_ast.rs | 24 +++- dsl/src/dsl_cst.rs | 177 ++++++++++++++++++++++++++++- dsl/src/dsl_domain.rs | 4 +- dsl/src/dsl_iter.rs | 24 +++- dsl/src/dsl_test.rs | 124 +++++++++++++++++++++ dsl/src/dsl_token.rs | 245 ----------------------------------------- dsl/src/dsl_value.rs | 84 ++++++++++++++ dsl/src/lib.rs | 137 +---------------------- input/src/input_dsl.rs | 25 ++--- input/src/lib.rs | 1 + output/src/ops_dsl.rs | 160 ++++++++++++--------------- 11 files changed, 513 insertions(+), 492 deletions(-) create mode 100644 dsl/src/dsl_test.rs create mode 100644 dsl/src/dsl_value.rs diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index fca3fd6..24a0f6f 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -1,13 +1,16 @@ use crate::*; use std::sync::Arc; use std::borrow::Cow; + #[derive(Debug, Clone, Default, PartialEq)] pub struct Ast(pub Value, AstIter>); + impl<'source> From> for Ast { fn from (token: CstToken<'source>) -> Self { token.value().into() } } + impl<'source> From> for Ast { fn from (value: CstValue<'source>) -> Self { use Value::*; @@ -23,10 +26,21 @@ impl<'source> From> for Ast { } } -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct AstIter; // TODO -impl<'source> From> for AstIter { - fn from (source: SourceIter<'source>) -> Self { - Self // TODO +impl DslValue for Ast { + type Str = Arc; + type Exp = AstIter; + fn value (&self) -> &Value, AstIter> { + self.0.value() + } +} + +impl DslToken for Ast { + type Value = Self; + type Meta = (); + fn value (&self) -> &Self::Value { + self + } + fn meta (&self) -> &Self::Meta { + &() } } diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs index 1af6f8f..7757a70 100644 --- a/dsl/src/dsl_cst.rs +++ b/dsl/src/dsl_cst.rs @@ -1,12 +1,183 @@ use crate::*; + /// CST stores strings as source references and expressions as new [SourceIter] instances. pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; -/// Token sharing memory with source reference. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); + /// Reference to the source slice. #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { pub source: &'source str, pub start: usize, pub length: usize, } + +/// Token sharing memory with source reference. +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); + +impl<'source> CstToken<'source> { + pub const fn new ( + source: &'source str, start: usize, length: usize, value: CstValue<'source> + ) -> Self { + Self(value, CstMeta { source, start, length }) + } + pub const fn end (&self) -> usize { + self.1.start.saturating_add(self.1.length) + } + pub const fn slice (&'source self) -> &'source str { + self.slice_source(self.1.source) + } + pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.1.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { + str_range(source, self.1.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: CstValue<'source>) -> Self { + Self(value, self.1) + } + pub const fn value (&self) -> CstValue<'source> { + self.0 + } + pub const fn error (self, error: DslError) -> Self { + Self(Value::Err(error), self.1) + } + pub const fn grow (self) -> Self { + Self(self.0, CstMeta { length: self.1.length.saturating_add(1), ..self.1 }) + } + pub const fn grow_num (self, m: usize, c: char) -> Self { + use Value::*; + match to_digit(c) { + Result::Ok(n) => Self(Num(10*m+n), self.grow().1), + Result::Err(e) => Self(Err(e), self.grow().1), + } + } + pub const fn grow_key (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Key(token.slice_source(self.1.source))) + } + pub const fn grow_sym (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Sym(token.slice_source(self.1.source))) + } + pub const fn grow_str (self) -> Self { + use Value::*; + let token = self.grow(); + token.with_value(Str(token.slice_source(self.1.source))) + } + pub const fn grow_exp (self) -> Self { + use Value::*; + let token = self.grow(); + if let Exp(depth, _) = token.value() { + token.with_value(Exp(depth, SourceIter::new(token.slice_source_exp(self.1.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value() { + token.with_value(Value::Exp(depth.saturating_add(1), source)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let Value::Exp(depth, source) = token.value() { + if depth > 0 { + token.with_value(Value::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) +} + +/// Static iteration helper. +#[macro_export] macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +pub const fn peek_src <'a> (source: &'a str) -> Option> { + use Value::*; + let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); + iterate!(char_indices(source) => (start, c) => token = match token.value() { + Err(_) => return Some(token), + Nil => match c { + ' '|'\n'|'\r'|'\t' => + token.grow(), + '(' => + CstToken::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), + '"' => + CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), + '0'..='9' => + CstToken::new(source, start, 1, match to_digit(c) { + Ok(c) => Value::Num(c), + Result::Err(e) => Value::Err(e) + }), + _ => token.error(Unexpected(c)) + }, + Str(_) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, + Num(n) => match c { + '0'..='9' => token.grow_num(n, c), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Sym(_) => match c { + 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Key(_) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Exp(depth, _) => match depth { + 0 => return Some(token.grow_exp()), + _ => match c { + ')' => token.grow_out(), + '(' => token.grow_in(), + _ => token.grow_exp(), + } + }, + }); + match token.value() { + Nil => None, + _ => Some(token), + } +} diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs index 8289ff8..d8d502e 100644 --- a/dsl/src/dsl_domain.rs +++ b/dsl/src/dsl_domain.rs @@ -27,9 +27,9 @@ pub trait Eval { /// May construct [Self] from token stream. pub trait Dsl: Sized { - fn try_provide (state: &State, source: impl DslValue) -> Perhaps; + fn try_provide (state: &State, source: Ast) -> Perhaps; fn provide >, F: Fn()->E> ( - state: &State, source: impl DslValue, error: F + state: &State, source: Ast, error: F ) -> Usually { let next = format!("{source:?}"); if let Some(value) = Self::try_provide(state, source)? { diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index b532648..07cfdd7 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -4,7 +4,29 @@ pub trait DslIter { type Token: DslToken; fn peek (&self) -> Option<::Value>; fn next (&mut self) -> Option<::Value>; - fn rest (&self) -> Option<&[::Value]>; + fn rest (self) -> Vec; +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct AstIter(std::collections::VecDeque); + +impl DslIter for AstIter { + type Token = Ast; + fn peek (&self) -> Option { + self.0.get(0).cloned() + } + fn next (&mut self) -> Option { + self.0.pop_front() + } + fn rest (self) -> Vec { + self.0.into() + } +} + +impl<'source> From> for AstIter { + fn from (source: SourceIter<'source>) -> Self { + Self(source.map(Into::into).collect()) + } } /// Provides a native [Iterator] API over [SourceConstIter], diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs new file mode 100644 index 0000000..4f7c4b2 --- /dev/null +++ b/dsl/src/dsl_test.rs @@ -0,0 +1,124 @@ +#[cfg(test)] mod test_token_iter { + use crate::*; + //use proptest::prelude::*; + #[test] fn test_iters () { + let mut iter = crate::SourceIter::new(&":foo :bar"); + let _ = iter.next(); + let mut iter = crate::TokenIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] const fn test_const_iters () { + let mut iter = crate::SourceIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] fn test_num () { + let digit = to_digit('0'); + let digit = to_digit('x'); + let number = to_number(&"123"); + let number = to_number(&"12asdf3"); + } + //proptest! { + //#[test] fn proptest_source_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::SourceIter::new(&source); + ////let _ = iter.next(); + //} + //#[test] fn proptest_token_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::TokenIter::new(&source); + ////let _ = iter.next(); + //} + //} +} + +#[cfg(test)] mod test_token_prop { + use proptest::prelude::*; + proptest! { + #[test] fn test_token_prop ( + source in "\\PC*", + start in usize::MIN..usize::MAX, + length in usize::MIN..usize::MAX, + ) { + let token = crate::Token { + source: &source, + start, + length, + value: crate::Value::Nil + }; + let _ = token.slice(); + } + } +} + +#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { + let source = ":f00"; + let mut token = Token { source, start: 0, length: 1, value: Sym(":") }; + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") }); + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") }); + token = token.grow_sym(); + assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") }); + + let src = ""; + assert_eq!(None, SourceIter(src).next()); + + let src = " \n \r \t "; + assert_eq!(None, SourceIter(src).next()); + + let src = "7"; + assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value); + + let src = " 100 "; + assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value); + + let src = " 9a "; + assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value); + + let src = " :123foo "; + assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value); + + let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t"; + assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value); + + let src = "foo123"; + assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value); + + let src = "foo/bar"; + assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); + + let src = "\"foo/bar\""; + assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); + + let src = " \"foo/bar\" "; + assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); + + Ok(()) +} + +//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { + //// 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. + //let mut view = TokenIter(source); + //// The token iterator wraps a const token+source iterator. + //assert_eq!(view.0.0, source); + //let mut expr = view.peek(); + //assert_eq!(view.0.0, source); + //assert_eq!(expr, Some(Token { + //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..])) + //})); + ////panic!("{view:?}"); + ////panic!("{:#?}", expr); + ////for example in [ + ////include_str!("../../tui/examples/edn01.edn"), + ////include_str!("../../tui/examples/edn02.edn"), + ////] { + //////let items = Dsl::read_all(example)?; + //////panic!("{layout:?}"); + //////let content = >::from(&layout); + ////} + //Ok(()) +//} diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs index 71d1997..4411e7e 100644 --- a/dsl/src/dsl_token.rs +++ b/dsl/src/dsl_token.rs @@ -1,67 +1,5 @@ use crate::*; -#[derive(PartialEq, Clone, Default, Debug)] -pub enum Value< - S: PartialEq + Clone + Default + Debug, - X: PartialEq + Clone + Default + Debug, -> { - #[default] Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(usize, X), -} - -impl< - S: Copy + PartialEq + Clone + Default + Debug, - X: Copy + PartialEq + Clone + Default + Debug, -> Copy for Value {} - -pub trait DslValue: PartialEq + Clone + Default + Debug { - type Err: PartialEq + Clone + Debug; - type Num: PartialEq + Copy + Clone + Default + Debug; - type Str: PartialEq + Clone + Default + Debug; - type Exp: PartialEq + Clone + Default + Debug; - fn nil (&self) -> bool; - fn err (&self) -> Option<&Self::Err>; - fn num (&self) -> Option; - fn sym (&self) -> Option<&Self::Str>; - fn key (&self) -> Option<&Self::Str>; - fn str (&self) -> Option<&Self::Str>; - fn exp (&self) -> Option<&Self::Exp>; - fn exp_depth (&self) -> Option { None } // TODO - fn exp_head (&self) -> Option<&Self> { None } // TODO - fn exp_tail (&self) -> Option<&[Self]> { None } // TODO -} - -impl< - Str: PartialEq + Clone + Default + Debug, - Exp: PartialEq + Clone + Default + Debug, -> DslValue for Value { - type Err = DslError; - type Num = usize; - type Str = Str; - type Exp = Exp; - fn nil (&self) -> bool { - matches!(self, Self::Nil) - } - fn err (&self) -> Option<&DslError> { - if let Self::Err(e) = self { Some(e) } else { None } - } - fn num (&self) -> Option { - if let Self::Num(n) = self { Some(*n) } else { None } - } - fn sym (&self) -> Option<&Str> { - if let Self::Sym(s) = self { Some(s) } else { None } - } - fn key (&self) -> Option<&Str> { - if let Self::Key(k) = self { Some(k) } else { None } - } - fn str (&self) -> Option<&Str> { - if let Self::Str(s) = self { Some(s) } else { None } - } - fn exp (&self) -> Option<&Exp> { - if let Self::Exp(_, x) = self { Some(x) } else { None } - } -} - - #[derive(PartialEq, Clone, Default, Debug)] pub struct Token< V: PartialEq + Clone + Default + Debug, @@ -85,186 +23,3 @@ impl DslToken for Token CstToken<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: CstValue<'source> - ) -> Self { - Self(value, CstMeta { source, start, length }) - } - pub const fn end (&self) -> usize { - self.1.start.saturating_add(self.1.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.1.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.1.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.1.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: CstValue<'source>) -> Self { - Self(value, self.1) - } - pub const fn value (&self) -> CstValue<'source> { - self.0 - } - pub const fn error (self, error: DslError) -> Self { - Self(Value::Err(error), self.1) - } - pub const fn grow (self) -> Self { - Self(self.0, CstMeta { length: self.1.length.saturating_add(1), ..self.1 }) - } - pub const fn grow_num (self, m: usize, c: char) -> Self { - use Value::*; - match to_digit(c) { - Result::Ok(n) => Self(Num(10*m+n), self.grow().1), - Result::Err(e) => Self(Err(e), self.grow().1), - } - } - pub const fn grow_key (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Key(token.slice_source(self.1.source))) - } - pub const fn grow_sym (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.1.source))) - } - pub const fn grow_str (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Str(token.slice_source(self.1.source))) - } - pub const fn grow_exp (self) -> Self { - use Value::*; - let token = self.grow(); - if let Exp(depth, _) = token.value() { - token.with_value(Exp(depth, SourceIter::new(token.slice_source_exp(self.1.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value() { - token.with_value(Value::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(Value::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} - -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'a> (source: &'a str) -> Option> { - use Value::*; - let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - CstToken::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), - '"' => - CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstToken::new(source, start, 1, match to_digit(c) { - Ok(c) => Value::Num(c), - Result::Err(e) => Value::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -//impl, X1, X2: From> From> for Value { - //fn from (other: Value) -> Self { - //use Value::*; - //match other { - //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(x) => Exp(x.into()), - //} - //} -//} diff --git a/dsl/src/dsl_value.rs b/dsl/src/dsl_value.rs new file mode 100644 index 0000000..45b586f --- /dev/null +++ b/dsl/src/dsl_value.rs @@ -0,0 +1,84 @@ +use crate::*; +use std::fmt::Display; +pub trait DslValue: PartialEq + Clone + Default + Debug { + type Str: AsRef + PartialEq + Clone + Default + Debug; + type Exp: PartialEq + Clone + Default + Debug; + fn value (&self) -> &Value; + fn nil (&self) -> bool { + matches!(self.value(), Value::Nil) + } + fn err (&self) -> Option<&DslError> { + if let Value::Err(e) = self.value() { Some(e) } else { None } + } + fn num (&self) -> Option { + if let Value::Num(n) = self.value() { Some(*n) } else { None } + } + fn sym (&self) -> Option<&str> { + if let Value::Sym(s) = self.value() { Some(s.as_ref()) } else { None } + } + fn key (&self) -> Option<&str> { + if let Value::Key(k) = self.value() { Some(k.as_ref()) } else { None } + } + fn str (&self) -> Option<&str> { + if let Value::Str(s) = self.value() { Some(s.as_ref()) } else { None } + } + fn exp (&self) -> Option<&Self::Exp> { + if let Value::Exp(_, x) = self.value() { Some(x) } else { None } + } + fn exp_depth (&self) -> Option { None } // TODO + fn exp_head (&self) -> Option<&Self> { None } // TODO + fn exp_tail (&self) -> Option<&[Self]> { None } // TODO +} +impl< + Str: AsRef + PartialEq + Clone + Default + Debug, + Exp: PartialEq + Clone + Default + Debug, +> DslValue for Value { + type Str = Str; + type Exp = Exp; + fn value (&self) -> &Value { + self + } +} + +pub enum Value { Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(usize, X), } +impl Default for Value { fn default () -> Self { Self:: Nil } } +impl PartialEq for Value { + fn eq (&self, other: &Self) -> bool { + use Value::*; + match (self, other) { + (Nil, Nil) => true, + (Err(e1), Err(e2)) if e1 == e2 => true, + (Num(n1), Num(n2)) if n1 == n2 => true, + (Sym(s1), Sym(s2)) if s1 == s2 => true, + (Key(s1), Key(s2)) if s1 == s2 => true, + (Str(s1), Str(s2)) if s1 == s2 => true, + (Exp(d1, e1), Exp(d2, e2)) if d1 == d2 && e1 == e2 => true, + _ => false + } + } +} +impl Clone for Value { + fn clone (&self) -> Self { + use Value::*; + match self { + Nil => Nil, + Err(e) => Err(e.clone()), + Num(n) => Num(*n), + Sym(s) => Sym(s.clone()), + Key(s) => Key(s.clone()), + Str(s) => Str(s.clone()), + Exp(d, e) => Exp(*d, e.clone()), + } + } +} +impl Copy for Value {} +impl Debug for Value { + fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + todo!() + } +} +impl Display for Value { + fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + todo!() + } +} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 8fc06e4..7e990c8 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,7 +1,3 @@ -#![feature(adt_const_params)] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_fn_trait_return)] - //! [Token]s are parsed substrings with an associated [Value]. //! //! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] @@ -35,15 +31,15 @@ //! assert_eq!(view.0.0, src); //! assert_eq!(view.peek(), view.0.peek()) //! ``` - +#![feature(adt_const_params)] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_fn_trait_return)] pub(crate) use ::tengri_core::*; - pub(crate) use std::fmt::Debug; 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::*; - mod dsl_ast; pub use self::dsl_ast::*; mod dsl_cst; pub use self::dsl_cst::*; mod dsl_display; pub use self::dsl_display::*; @@ -51,128 +47,5 @@ mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; mod dsl_iter; pub use self::dsl_iter::*; mod dsl_token; pub use self::dsl_token::*; - -#[cfg(test)] mod test_token_iter { - use crate::*; - //use proptest::prelude::*; - #[test] fn test_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - let mut iter = crate::TokenIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] const fn test_const_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] fn test_num () { - let digit = to_digit('0'); - let digit = to_digit('x'); - let number = to_number(&"123"); - let number = to_number(&"12asdf3"); - } - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::SourceIter::new(&source); - ////let _ = iter.next(); - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} -} - -#[cfg(test)] mod test_token_prop { - use proptest::prelude::*; - proptest! { - #[test] fn test_token_prop ( - source in "\\PC*", - start in usize::MIN..usize::MAX, - length in usize::MIN..usize::MAX, - ) { - let token = crate::Token { - source: &source, - start, - length, - value: crate::Value::Nil - }; - let _ = token.slice(); - } - } -} - -#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { - let source = ":f00"; - let mut token = Token { source, start: 0, length: 1, value: Sym(":") }; - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") }); - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") }); - token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") }); - - let src = ""; - assert_eq!(None, SourceIter(src).next()); - - let src = " \n \r \t "; - assert_eq!(None, SourceIter(src).next()); - - let src = "7"; - assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value); - - let src = " 100 "; - assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value); - - let src = " 9a "; - assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value); - - let src = " :123foo "; - assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value); - - let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t"; - assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value); - - let src = "foo123"; - assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value); - - let src = "foo/bar"; - assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); - - let src = "\"foo/bar\""; - assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); - - let src = " \"foo/bar\" "; - assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); - - Ok(()) -} - -//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { - //// 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. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} +mod dsl_value; pub use self::dsl_value::*; +#[cfg(test)] mod dsl_test; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index bff457d..a91453f 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -23,34 +23,27 @@ impl InputLayers { self.0.push(InputLayer(condition, binding)); self } - pub fn handle , I, O: Command> (&self, state: &mut S, input: I) -> Perhaps { + pub fn handle + Eval, I: Eval, O: Command> (&self, state: &mut S, input: I) -> Perhaps { InputHandle(state, self.0.as_slice()).try_eval(input) } } pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); -impl<'a, S: Eval, I, O: Command> Eval for InputHandle<'a, S> { +impl<'a, S: Eval + Eval, I: Eval, O: Command> Eval for InputHandle<'a, S> { fn try_eval (&self, input: I) -> Perhaps { let Self(state, layers) = self; for InputLayer(condition, binding) in layers.iter() { let mut matches = true; if let Some(condition) = condition { - matches = state.eval(*condition, ||"input: no condition")?; + matches = state.eval(condition.clone(), ||"input: no condition")?; } - if matches { - if let AstValue::Exp(e) = binding.peek() { - if let Some(ast) = e.peek() { - if input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? - && let Some(command) = state.try_eval(ast)? { - return Ok(Some(command)) - } - } else { - unreachable!("InputLayer") - } - } else { - panic!("InputLayer: expected expression, got: {input:?}") - } + if matches + && let Some(exp) = binding.exp() + && let Some(ast) = exp.peek() + && input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? + && let Some(command) = state.try_eval(ast)? { + return Ok(Some(command)) } } Ok(None) diff --git a/input/src/lib.rs b/input/src/lib.rs index 21035fe..4f27011 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,4 +1,5 @@ #![feature(associated_type_defaults)] +#![feature(if_let_guard)] pub(crate) use tengri_core::*; diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index f14194b..196f693 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -1,134 +1,118 @@ use crate::*; +use Value::*; impl Dsl for When where S: Eval + Eval { - fn try_provide (state: S, source: Ast) -> Perhaps { - if let Ast::Exp(exp) = source { - let mut iter = source.clone(); - if let Some(Ast::Key(id)) = iter.next() && *id == *"when" { - return Ok(Some(Self( - state.eval(iter.next().unwrap(), ||"when: expected condition")?, - state.eval(iter.next().unwrap(), ||"when: expected content")?, - ))) - } + fn try_provide (state: &S, source: Ast) -> Perhaps { + if let Exp(_, exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { + let _ = exp.next(); + return Ok(Some(Self( + state.eval(exp.next().unwrap(), ||"when: expected condition")?, + state.eval(exp.next().unwrap(), ||"when: expected content")?, + ))) } Ok(None) } } impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: S, source: Ast) -> Perhaps { - if let Ast::Exp(source) = source { - let mut iter = source.clone(); - if let Some(Ast::Key(id)) = iter.next() && *id == *"either" { - return Ok(Some(Self( - state.eval(iter.next().unwrap(), ||"either: expected condition")?, - state.eval(iter.next().unwrap(), ||"either: expected content 1")?, - state.eval(iter.next().unwrap(), ||"either: expected content 2")?, - ))) - } + fn try_provide (state: &S, source: Ast) -> Perhaps { + if let Exp(_, exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { + let _ = exp.next(); + return Ok(Some(Self( + state.eval(exp.next().unwrap(), ||"either: expected condition")?, + state.eval(exp.next().unwrap(), ||"either: expected content 1")?, + state.eval(exp.next().unwrap(), ||"either: expected content 2")?, + ))) + } + Ok(None) + } +} + +impl Dsl for Align where S: Eval, A> { + fn try_provide (state: &S, source: Ast) -> Perhaps { + if let Exp(_, source) = source.0 { + let mut rest = source.clone(); + return Ok(Some(match rest.next().and_then(|x|x.key()) { + Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: expected content")?), + Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: expected content")?), + Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: expected content")?), + Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: expected content")?), + Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: expected content")?), + Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: expected content")?), + Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: expected content")?), + Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: expected content")?), + Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: expected content")?), + Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: expected content")?), + Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: expected content")?), + _ => return Ok(None), + })) } Ok(None) } } impl Dsl for Bsp where S: Eval, A> + Eval, B> { - fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(Some(if let Ast::Exp(source) = source { - let mut iter = source.clone(); - match iter.next() { - Some(Value::Key("bsp/n")) => Self::n( - state.eval(iter.next(), ||"bsp/n: expected content 1")?, - state.eval(iter.next(), ||"bsp/n: expected content 2")?, + fn try_provide (state: &S, source: Ast) -> Perhaps { + if let Exp(_, exp) = source.0 { + let mut rest = exp.clone(); + return Ok(Some(match rest.next().and_then(|x|x.key()) { + Some("bsp/n") => Self::n( + state.eval(rest.next(), ||"bsp/n: expected content 1")?, + state.eval(rest.next(), ||"bsp/n: expected content 2")?, ), - Some(Value::Key("bsp/s")) => Self::s( - state.eval(iter.next(), ||"bsp/s: expected content 1")?, - state.eval(iter.next(), ||"bsp/s: expected content 2")?, + Some("bsp/s") => Self::s( + state.eval(rest.next(), ||"bsp/s: expected content 1")?, + state.eval(rest.next(), ||"bsp/s: expected content 2")?, ), - Some(Value::Key("bsp/e")) => Self::e( - state.eval(iter.next(), ||"bsp/e: expected content 1")?, - state.eval(iter.next(), ||"bsp/e: expected content 2")?, + Some("bsp/e") => Self::e( + state.eval(rest.next(), ||"bsp/e: expected content 1")?, + state.eval(rest.next(), ||"bsp/e: expected content 2")?, ), - Some(Value::Key("bsp/w")) => Self::w( - state.eval(iter.next(), ||"bsp/w: expected content 1")?, - state.eval(iter.next(), ||"bsp/w: expected content 2")?, + Some("bsp/w") => Self::w( + state.eval(rest.next(), ||"bsp/w: expected content 1")?, + state.eval(rest.next(), ||"bsp/w: expected content 2")?, ), - Some(Value::Key("bsp/a")) => Self::a( - state.eval(iter.next(), ||"bsp/a: expected content 1")?, - state.eval(iter.next(), ||"bsp/a: expected content 2")?, + Some("bsp/a") => Self::a( + state.eval(rest.next(), ||"bsp/a: expected content 1")?, + state.eval(rest.next(), ||"bsp/a: expected content 2")?, ), - Some(Value::Key("bsp/b")) => Self::b( - state.eval(iter.next(), ||"bsp/b: expected content 1")?, - state.eval(iter.next(), ||"bsp/b: expected content 2")?, + Some("bsp/b") => Self::b( + state.eval(rest.next(), ||"bsp/b: expected content 1")?, + state.eval(rest.next(), ||"bsp/b: expected content 2")?, ), _ => return Ok(None), - } - } else { - return Ok(None) - })) - } -} - -impl Dsl for Align where S: Eval, A> { - fn try_provide (state: S, source: Ast) -> Perhaps { - Ok(Some(if let Ast::Exp(source) = source { - let mut iter = source.clone(); - match iter.next() { - Some(Value::Key("align/c")) => - Self::c(state.eval(iter.next(), ||"align/c: expected content")?), - Some(Value::Key("align/x")) => - Self::x(state.eval(iter.next(), ||"align/x: expected content")?), - Some(Value::Key("align/y")) => - Self::y(state.eval(iter.next(), ||"align/y: expected content")?), - Some(Value::Key("align/n")) => - Self::n(state.eval(iter.next(), ||"align/n: expected content")?), - Some(Value::Key("align/s")) => - Self::s(state.eval(iter.next(), ||"align/s: expected content")?), - Some(Value::Key("align/e")) => - Self::e(state.eval(iter.next(), ||"align/e: expected content")?), - Some(Value::Key("align/w")) => - Self::w(state.eval(iter.next(), ||"align/w: expected content")?), - Some(Value::Key("align/nw")) => - Self::nw(state.eval(iter.next(), ||"align/nw: expected content")?), - Some(Value::Key("align/ne")) => - Self::ne(state.eval(iter.next(), ||"align/ne: expected content")?), - Some(Value::Key("align/sw")) => - Self::sw(state.eval(iter.next(), ||"align/sw: expected content")?), - Some(Value::Key("align/se")) => - Self::se(state.eval(iter.next(), ||"align/se: expected content")?), - _ => return Ok(None), - } - } else { - return Ok(None) - })) + })) + } + Ok(None) } } //#[cfg(feature = "dsl")] take!($Enum, A|state, words|Ok( - //if let Some(Token { value: Value::Key(k), .. }) = words.peek() { + //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: Value::Key($x),..}) => Self::x(content), - //Some(Token{value: Value::Key($y),..}) => Self::y(content), - //Some(Token{value: Value::Key($xy),..}) => Self::xy(content), + //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: Value::Key($x|$y|$xy), .. }) = words.peek() { + //if let Some(Token { value: Key($x|$y|$xy), .. }) = words.peek() { //let mut base = words.clone(); //Some(match words.next() { - //Some(Token { value: Value::Key($x), .. }) => Self::x( + //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: Value::Key($y), .. }) => Self::y( + //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: Value::Key($x), .. }) => Self::xy( + //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")? From 08593571fa01bba8514b2e484a0c7387b6645e23 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 8 Jun 2025 04:45:14 +0300 Subject: [PATCH 107/178] output: fix expressions --- output/src/ops_dsl.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index 196f693..5d131bb 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -3,7 +3,7 @@ use Value::*; impl Dsl for When where S: Eval + Eval { fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { + if let Exp(_, mut exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { let _ = exp.next(); return Ok(Some(Self( state.eval(exp.next().unwrap(), ||"when: expected condition")?, @@ -16,7 +16,7 @@ impl Dsl for When where S: Eval + Eval { impl Dsl for Either where S: Eval + Eval + Eval { fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { + if let Exp(_, mut exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { let _ = exp.next(); return Ok(Some(Self( state.eval(exp.next().unwrap(), ||"either: expected condition")?, @@ -32,7 +32,7 @@ impl Dsl for Align where S: Eval, A> { fn try_provide (state: &S, source: Ast) -> Perhaps { if let Exp(_, source) = source.0 { let mut rest = source.clone(); - return Ok(Some(match rest.next().and_then(|x|x.key()) { + return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { Some("align/c") => Self::c(state.eval(rest.next(), ||"align/c: expected content")?), Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: expected content")?), Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: expected content")?), @@ -55,7 +55,7 @@ impl Dsl for Bsp where S: Eval, A> + Eval Perhaps { if let Exp(_, exp) = source.0 { let mut rest = exp.clone(); - return Ok(Some(match rest.next().and_then(|x|x.key()) { + return Ok(Some(match rest.next().as_ref().and_then(|x|x.key()) { Some("bsp/n") => Self::n( state.eval(rest.next(), ||"bsp/n: expected content 1")?, state.eval(rest.next(), ||"bsp/n: expected content 2")?, From 21832453d9554752a77278385446518744baeee8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 12 Jun 2025 19:29:12 +0300 Subject: [PATCH 108/178] dsl,input,output,proc,tui: fix warnings --- dsl/src/dsl_ast.rs | 1 - dsl/src/dsl_display.rs | 2 +- dsl/src/dsl_value.rs | 4 ++-- dsl/src/lib.rs | 2 +- input/src/input_dsl.rs | 2 +- output/src/ops.rs | 2 +- output/src/ops/memo.rs | 2 +- output/src/ops/stack.rs | 2 +- proc/src/proc_command.rs | 8 ++++---- proc/src/proc_view.rs | 4 ++-- tui/src/tui_content.rs | 4 ++-- tui/src/tui_content/tui_button.rs | 2 +- tui/src/tui_content/tui_field.rs | 4 ++-- 13 files changed, 19 insertions(+), 20 deletions(-) diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs index 24a0f6f..c28d73a 100644 --- a/dsl/src/dsl_ast.rs +++ b/dsl/src/dsl_ast.rs @@ -1,6 +1,5 @@ use crate::*; use std::sync::Arc; -use std::borrow::Cow; #[derive(Debug, Clone, Default, PartialEq)] pub struct Ast(pub Value, AstIter>); diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs index aaae8c7..f86b4be 100644 --- a/dsl/src/dsl_display.rs +++ b/dsl/src/dsl_display.rs @@ -1,5 +1,5 @@ use crate::*; -use std::fmt::{Debug, Display, Formatter, Error as FormatError}; +use std::fmt::{Display, Formatter, Error as FormatError}; impl Display for Ast { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { use Value::*; diff --git a/dsl/src/dsl_value.rs b/dsl/src/dsl_value.rs index 45b586f..f06cf84 100644 --- a/dsl/src/dsl_value.rs +++ b/dsl/src/dsl_value.rs @@ -73,12 +73,12 @@ impl Clone for Value { } impl Copy for Value {} impl Debug for Value { - fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { todo!() } } impl Display for Value { - fn fmt (&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { todo!() } } diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 7e990c8..24a5ee0 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -42,7 +42,7 @@ pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; mod dsl_ast; pub use self::dsl_ast::*; mod dsl_cst; pub use self::dsl_cst::*; -mod dsl_display; pub use self::dsl_display::*; +mod dsl_display; //pub use self::dsl_display::*; mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; mod dsl_iter; pub use self::dsl_iter::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index a91453f..d34a8f7 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,5 +1,5 @@ use crate::*; -use std::marker::PhantomData; +//use std::marker::PhantomData; use std::fmt::Debug; #[derive(Default, Debug)] pub struct InputLayers(Vec); diff --git a/output/src/ops.rs b/output/src/ops.rs index cfeb23e..88a2ccb 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -53,7 +53,7 @@ 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 transform; //pub use self::transform::*; /// Renders multiple things on top of each other, #[macro_export] macro_rules! lay { diff --git a/output/src/ops/memo.rs b/output/src/ops/memo.rs index 07e2d93..8168f2d 100644 --- a/output/src/ops/memo.rs +++ b/output/src/ops/memo.rs @@ -1,4 +1,4 @@ -use crate::*; +//use crate::*; use std::sync::{Arc, RwLock}; #[derive(Debug, Default)] pub struct Memo { diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs index 6498ab0..59930ce 100644 --- a/output/src/ops/stack.rs +++ b/output/src/ops/stack.rs @@ -24,7 +24,7 @@ impl Stack { } } impl)->())->()> Content for Stack { - fn layout (&self, mut to: E::Area) -> E::Area { + fn layout (&self, to: E::Area) -> E::Area { let mut x = to.x(); let mut y = to.y(); let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index b4ab9d7..340ca1f 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -73,16 +73,16 @@ impl ToTokens for CommandDef { out }); - let matchers = exposed.values().map(|arm|{ + 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; + 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() { + for (arg, _ty) in arm.args() { write_quote_to(&mut out, quote! { #arg: Take::take_or_fail(self, words)?, }); @@ -193,7 +193,7 @@ impl CommandArm { unreachable!("only typed args should be present at this position"); }) } - fn to_enum_variant_def (&self) -> TokenStream2 { + fn _to_enum_variant_def (&self) -> TokenStream2 { let mut out = TokenStream2::new(); out.append(self.to_enum_variant_ident()); //let ident = &self.0; diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 9815c34..b4ea607 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -79,11 +79,11 @@ impl ToTokens for ViewDef { } } -fn builtins_with_holes () -> impl Iterator { +fn _builtins_with_holes () -> impl Iterator { builtins_with(quote! { _ }, quote! { _ }) } -fn builtins_with_boxes () -> impl Iterator { +fn _builtins_with_boxes () -> impl Iterator { builtins_with(quote! { _ }, quote! { Box> }) } diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 1b9cc1b..108d0b2 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -13,12 +13,12 @@ macro_rules! impl_content_layout_render { } mod tui_border; pub use self::tui_border::*; -mod tui_button; pub use self::tui_button::*; +mod tui_button; //pub use self::tui_button::*; mod tui_color; pub use self::tui_color::*; mod tui_field; pub use self::tui_field::*; mod tui_phat; pub use self::tui_phat::*; mod tui_repeat; pub use self::tui_repeat::*; -mod tui_number; pub use self::tui_number::*; +mod tui_number; //pub use self::tui_number::*; mod tui_scroll; pub use self::tui_scroll::*; mod tui_string; pub use self::tui_string::*; mod tui_style; pub use self::tui_style::*; diff --git a/tui/src/tui_content/tui_button.rs b/tui/src/tui_content/tui_button.rs index ec58935..53e546e 100644 --- a/tui/src/tui_content/tui_button.rs +++ b/tui/src/tui_content/tui_button.rs @@ -1 +1 @@ -use crate::{*, Color::*}; +//use crate::{*, Color::*}; diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index 3823182..a60cc32 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -3,7 +3,7 @@ use crate::*; pub struct FieldH(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldH { fn content (&self) -> impl Render { - let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; + let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self; row!( Tui::fg_bg(dark.rgb, darkest.rgb, "▐"), Tui::fg_bg(lightest.rgb, dark.rgb, title), @@ -16,7 +16,7 @@ impl, U: Content> Content for FieldH { pub struct FieldV(pub ItemTheme, pub T, pub U); impl, U: Content> Content for FieldV { fn content (&self) -> impl Render { - let Self(ItemTheme { darkest, dark, lighter, lightest, .. }, title, value) = self; + let Self(ItemTheme { darkest, dark, lightest, .. }, title, value) = self; Bsp::n( Align::w(Tui::bg(darkest.rgb, Tui::fg(lightest.rgb, Tui::bold(true, value)))), Fill::x(Align::w(row!( From 17506726cb325a6f495828ec5e3dd8a796c40913 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 12 Jun 2025 21:17:08 +0300 Subject: [PATCH 109/178] wip: updating tests --- Justfile | 2 + dsl/src/dsl_cst.rs | 8 +-- dsl/src/dsl_iter.rs | 90 ++++++++++++------------ dsl/src/dsl_test.rs | 111 +++++++++++++----------------- input/src/lib.rs | 2 +- output/src/lib.rs | 1 + output/src/test.rs | 14 ++-- proc/src/lib.rs | 4 +- proc/src/proc_command.rs | 6 +- proc/src/proc_expose.rs | 4 +- proc/src/proc_view.rs | 9 +-- tengri/src/lib.rs | 94 +------------------------ tengri/src/test.rs | 97 ++++++++++++++++++++++++++ tui/examples/{ => edn}/edn01.edn | 0 tui/examples/{ => edn}/edn02.edn | 0 tui/examples/{ => edn}/edn03.edn | 0 tui/examples/{ => edn}/edn04.edn | 0 tui/examples/{ => edn}/edn05.edn | 0 tui/examples/{ => edn}/edn06.edn | 0 tui/examples/{ => edn}/edn07.edn | 0 tui/examples/{ => edn}/edn08.edn | 0 tui/examples/{ => edn}/edn09.edn | 0 tui/examples/{ => edn}/edn10.edn | 0 tui/examples/{ => edn}/edn11.edn | 0 tui/examples/{ => edn}/edn12.edn | 0 tui/examples/{ => edn}/edn13.edn | 0 tui/examples/{ => edn}/edn14.edn | 0 tui/examples/{ => edn}/edn15.edn | 0 tui/examples/{ => edn}/edn16.edn | 0 tui/examples/{ => edn}/edn17.edn | 0 tui/examples/{ => edn}/edn99.edn | 0 tui/examples/tui.rs | 71 +++++++++++-------- tui/src/lib.rs | 10 +-- tui/src/tui_content/tui_field.rs | 4 +- tui/src/tui_content/tui_number.rs | 4 +- tui/src/tui_engine/tui_input.rs | 20 +++--- 36 files changed, 280 insertions(+), 271 deletions(-) create mode 100644 tengri/src/test.rs rename tui/examples/{ => edn}/edn01.edn (100%) rename tui/examples/{ => edn}/edn02.edn (100%) rename tui/examples/{ => edn}/edn03.edn (100%) rename tui/examples/{ => edn}/edn04.edn (100%) rename tui/examples/{ => edn}/edn05.edn (100%) rename tui/examples/{ => edn}/edn06.edn (100%) rename tui/examples/{ => edn}/edn07.edn (100%) rename tui/examples/{ => edn}/edn08.edn (100%) rename tui/examples/{ => edn}/edn09.edn (100%) rename tui/examples/{ => edn}/edn10.edn (100%) rename tui/examples/{ => edn}/edn11.edn (100%) rename tui/examples/{ => edn}/edn12.edn (100%) rename tui/examples/{ => edn}/edn13.edn (100%) rename tui/examples/{ => edn}/edn14.edn (100%) rename tui/examples/{ => edn}/edn15.edn (100%) rename tui/examples/{ => edn}/edn16.edn (100%) rename tui/examples/{ => edn}/edn17.edn (100%) rename tui/examples/{ => edn}/edn99.edn (100%) diff --git a/Justfile b/Justfile index af61a41..e17aef3 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,8 @@ covfig := "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cov/cargo-test-%p-%m.profraw'" grcov-binary := "--binary-path ./target/coverage/deps/" grcov-ignore := "--ignore-not-existing --ignore '../*' --ignore \"/*\" --ignore 'target/*'" +bacon: + {{covfig}} bacon -s cov: {{covfig}} time cargo test -j4 --workspace --profile coverage rm -rf target/coverage/html || true diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs index 7757a70..cfae7e2 100644 --- a/dsl/src/dsl_cst.rs +++ b/dsl/src/dsl_cst.rs @@ -3,6 +3,10 @@ use crate::*; /// CST stores strings as source references and expressions as new [SourceIter] instances. pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; +/// Token sharing memory with source reference. +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); + /// Reference to the source slice. #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { pub source: &'source str, @@ -10,10 +14,6 @@ pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; pub length: usize, } -/// Token sharing memory with source reference. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); - impl<'source> CstToken<'source> { pub const fn new ( source: &'source str, start: usize, length: usize, value: CstValue<'source> diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs index 07cfdd7..2e0e47a 100644 --- a/dsl/src/dsl_iter.rs +++ b/dsl/src/dsl_iter.rs @@ -7,25 +7,32 @@ pub trait DslIter { fn rest (self) -> Vec; } -#[derive(Debug, Clone, Default, PartialEq)] -pub struct AstIter(std::collections::VecDeque); - -impl DslIter for AstIter { - type Token = Ast; - fn peek (&self) -> Option { - self.0.get(0).cloned() - } - fn next (&mut self) -> Option { - self.0.pop_front() - } - fn rest (self) -> Vec { - self.0.into() +/// Implement the const iterator pattern. +#[macro_export] macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } } } -impl<'source> From> for AstIter { - fn from (source: SourceIter<'source>) -> Self { - Self(source.map(Into::into).collect()) +/// Owns a reference to the source text. +/// [SourceConstIter::next] emits subsequent pairs of: +/// * a [CstToken] and +/// * the source text remaining +/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceConstIter<'source>(pub &'source str); + +impl<'source> From> for SourceIter<'source> { + fn from (source: SourceConstIter<'source>) -> Self{ + Self(source) } } @@ -71,35 +78,6 @@ impl<'source> Into> for SourceIter<'source> { } } -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -/// Owns a reference to the source text. -/// [SourceConstIter::next] emits subsequent pairs of: -/// * a [CstToken] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceConstIter<'source>(pub &'source str); - -impl<'source> From> for SourceIter<'source> { - fn from (source: SourceConstIter<'source>) -> Self{ - Self(source) - } -} - impl<'source> From<&'source str> for SourceConstIter<'source> { fn from (source: &'source str) -> Self{ Self::new(source) @@ -128,3 +106,25 @@ impl<'source> SourceConstIter<'source> { } const_iter!(<'source>|self: SourceConstIter<'source>| => CstToken<'source> => self.next_mut().map(|(result, _)|result)); + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct AstIter(std::collections::VecDeque); + +impl DslIter for AstIter { + type Token = Ast; + fn peek (&self) -> Option { + self.0.get(0).cloned() + } + fn next (&mut self) -> Option { + self.0.pop_front() + } + fn rest (self) -> Vec { + self.0.into() + } +} + +impl<'source> From> for AstIter { + fn from (source: SourceIter<'source>) -> Self { + Self(source.map(Into::into).collect()) + } +} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs index 4f7c4b2..2f5ce15 100644 --- a/dsl/src/dsl_test.rs +++ b/dsl/src/dsl_test.rs @@ -1,21 +1,21 @@ +use crate::*; + #[cfg(test)] mod test_token_iter { use crate::*; //use proptest::prelude::*; #[test] fn test_iters () { let mut iter = crate::SourceIter::new(&":foo :bar"); let _ = iter.next(); - let mut iter = crate::TokenIter::new(&":foo :bar"); - let _ = iter.next(); } #[test] const fn test_const_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); + let iter = crate::SourceConstIter::new(&":foo :bar"); let _ = iter.next(); } #[test] fn test_num () { - let digit = to_digit('0'); - let digit = to_digit('x'); - let number = to_number(&"123"); - let number = to_number(&"12asdf3"); + let _digit = to_digit('0'); + let _digit = to_digit('x'); + let _number = to_number(&"123"); + let _number = to_number(&"12asdf3"); } //proptest! { //#[test] fn proptest_source_iter ( @@ -33,67 +33,54 @@ //} } -#[cfg(test)] mod test_token_prop { - use proptest::prelude::*; - proptest! { - #[test] fn test_token_prop ( - source in "\\PC*", - start in usize::MIN..usize::MAX, - length in usize::MIN..usize::MAX, - ) { - let token = crate::Token { - source: &source, - start, - length, - value: crate::Value::Nil - }; - let _ = token.slice(); - } - } -} +//#[cfg(test)] mod test_token_prop { + //use crate::{CstToken, CstMeta, Value::*}; + //use proptest::prelude::*; + //proptest! { + //#[test] fn test_token_prop ( + //source in "\\PC*", + //start in usize::MIN..usize::MAX, + //length in usize::MIN..usize::MAX, + //) { + //let token = CstToken(Nil, CstMeta { source: &source, start, length }); + //let _ = token.slice(); + //} + //} +//} #[cfg(test)] #[test] fn test_token () -> Result<(), Box> { + use crate::Value::*; let source = ":f00"; - let mut token = Token { source, start: 0, length: 1, value: Sym(":") }; + let mut token = CstToken(Sym(":"), CstMeta { source, start: 0, length: 1 }); token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 2, value: Sym(":f") }); + assert_eq!(token, CstToken(Sym(":f"), CstMeta { source, start: 0, length: 2, })); token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 3, value: Sym(":f0") }); + assert_eq!(token, CstToken(Sym(":f0"), CstMeta { source, start: 0, length: 3, })); token = token.grow_sym(); - assert_eq!(token, Token { source, start: 0, length: 4, value: Sym(":f00") }); + assert_eq!(token, CstToken(Sym(":f00"), CstMeta { source, start: 0, length: 4, })); - let src = ""; - assert_eq!(None, SourceIter(src).next()); - - let src = " \n \r \t "; - assert_eq!(None, SourceIter(src).next()); - - let src = "7"; - assert_eq!(Num(7), SourceIter(src).next().unwrap().0.value); - - let src = " 100 "; - assert_eq!(Num(100), SourceIter(src).next().unwrap().0.value); - - let src = " 9a "; - assert_eq!(Err(Unexpected('a')), SourceIter(src).next().unwrap().0.value); - - let src = " :123foo "; - assert_eq!(Sym(":123foo"), SourceIter(src).next().unwrap().0.value); - - let src = " \r\r\r\n\n\n@bar456\t\t\t\t\t\t"; - assert_eq!(Sym("@bar456"), SourceIter(src).next().unwrap().0.value); - - let src = "foo123"; - assert_eq!(Key("foo123"), SourceIter(src).next().unwrap().0.value); - - let src = "foo/bar"; - assert_eq!(Key("foo/bar"), SourceIter(src).next().unwrap().0.value); - - let src = "\"foo/bar\""; - assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); - - let src = " \"foo/bar\" "; - assert_eq!(Str("foo/bar"), SourceIter(src).next().unwrap().0.value); + assert_eq!(None, + SourceIter::new("").next()); + assert_eq!(None, + SourceIter::new(" \n \r \t ").next()); + assert_eq!(&Num(7), + SourceIter::new("7").next().unwrap().0.value()); + assert_eq!(&Num(100), + SourceIter::new(" 100 ").next().unwrap().0.value()); + assert_eq!(&Err(Unexpected('a')), + SourceIter::new(" 9a ").next().unwrap().0.value()); + assert_eq!(&Sym(":123foo"), + SourceIter::new(" :123foo ").next().unwrap().0.value()); + assert_eq!(&Sym("@bar456"), + SourceIter::new(" \r\r\r\n\n\n@bar456\t\t\t\t\t\t").next().unwrap().0.value()); + assert_eq!(&Key("foo123"), + SourceIter::new("foo123").next().unwrap().0.value()); + assert_eq!(&Key("foo/bar"), + SourceIter::new("foo/bar").next().unwrap().0.value()); + assert_eq!(&Str("foo/bar"), + SourceIter::new("\"foo/bar\"").next().unwrap().0.value()); + assert_eq!(&Str("foo/bar"), + SourceIter::new(" \"foo/bar\" ").next().unwrap().0.value()); Ok(()) } @@ -108,7 +95,7 @@ //let mut expr = view.peek(); //assert_eq!(view.0.0, source); //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter(&source[1..])) + //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter::new(&source[1..])) //})); ////panic!("{view:?}"); ////panic!("{:#?}", expr); diff --git a/input/src/lib.rs b/input/src/lib.rs index 4f27011..1324e15 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -33,6 +33,6 @@ mod input_handle; pub use self::input_handle::*; } #[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { - let keymap = SourceIter::new(""); + let _keymap = SourceIter::new(""); Ok(()) } diff --git a/output/src/lib.rs b/output/src/lib.rs index 30e8fff..5407df5 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -16,3 +16,4 @@ mod ops; pub use self::ops::*; mod output; pub use self::output::*; #[cfg(test)] mod test; +#[cfg(test)] pub use proptest_derive::Arbitrary; diff --git a/output/src/test.rs b/output/src/test.rs index 517aa64..d324664 100644 --- a/output/src/test.rs +++ b/output/src/test.rs @@ -1,5 +1,5 @@ -use crate::*; -use proptest_derive::Arbitrary; +use crate::{*, Direction::*}; +//use proptest_derive::Arbitrary; use proptest::{prelude::*, option::of}; proptest! { @@ -86,7 +86,7 @@ macro_rules! test_op_transform { if let Some(op) = match (op_x, op_y) { (Some(x), Some(y)) => Some($Op::xy(x, y, content)), (Some(x), None) => Some($Op::x(x, content)), - (Some(y), None) => Some($Op::y(y, content)), + (None, Some(y)) => Some($Op::y(y, content)), _ => None } { assert_eq!(Content::layout(&op, [x, y, w, h]), @@ -142,7 +142,7 @@ proptest! { fn area_mut (&mut self) -> &mut [u16;4] { &mut self.0 } - fn place (&mut self, _: [u16;4], _: &impl Render) { + fn place + ?Sized> (&mut self, _: [u16;4], _: &T) { () } } @@ -162,9 +162,9 @@ proptest! { #[test] fn test_iter_map () { struct Foo; impl Content for Foo {} - fn make_map + Send + Sync> (data: &Vec) -> impl Content { - Map::new(||data.iter(), |foo, index|{}) + fn _make_map + Send + Sync> (data: &Vec) -> impl Content { + Map::new(||data.iter(), |_foo, _index|{}) } - let data = vec![Foo, Foo, Foo]; + let _data = vec![Foo, Foo, Foo]; //let map = make_map(&data); } diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 02ba5de..bc39ee5 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -76,7 +76,7 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { assert_eq!(x.output, output); // TODO - let x: crate::proc_view::ViewImpl = pq! { + let _x: crate::proc_view::ViewImpl = pq! { impl Foo { /// docstring1 #[tengri::view(":view1")] #[bar] fn a_view () {} @@ -86,7 +86,7 @@ pub(crate) fn write_quote_to (out: &mut TokenStream2, quote: TokenStream2) { #[baz] fn is_not_view () {} } }; - let expected_target: Ident = pq! { Foo }; + let _expected_target: Ident = pq! { Foo }; //assert_eq!(x.target, expected_target); //assert_eq!(x.items.len(), 2); //assert_eq!(x.items[0].item, pq! { diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 340ca1f..078e794 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: Take::take_or_fail(self, words)?, + #arg: Dsl::try_provide(self, words)?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::Take<#state> for #command_enum { - fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { + 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(); todo!()//Ok(match token { #(#matchers)* _ => None }) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 873b639..2b64d1a 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -85,8 +85,8 @@ impl ToTokens for ExposeImpl { }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::Take<#state> for #t { - fn take (state: &#state, mut words: ::tengri::dsl::Cst) -> Perhaps { + 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)* diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index b4ea607..ef5aa49 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -45,7 +45,7 @@ impl ToTokens for ViewDef { // 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::take_or_fail(state, expr, ||"failed to load builtin")?.boxed() + #builtin::try_provide(state, expr, ||"failed to load builtin")?.boxed() )), }); // Symbols are handled by user-taked functions @@ -63,11 +63,8 @@ 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<'source, 'state: 'source> - Take<'state, 'source, #self_ty> - for Box + 'state> - { - fn take (state: &'state #self_ty, mut words: Cst<'source>) -> Perhaps { + impl<'state> ::tengri::dsl::Dsl + 'state>> for #self_ty { + fn try_provide (state: &'state #self_ty, mut words: ::tengri::dsl::Ast) -> Perhaps + 'state>> { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { match value { #(#builtin)* #(#exposed)* _ => None } } else { diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index d451e70..972a702 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -5,96 +5,4 @@ pub use ::tengri_core::*; #[cfg(feature="tui")] pub use ::tengri_tui as tui; #[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::tui::TuiIn; - use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; - //use crate::input::*; - //use crate::dsl::*; - struct Test { - keys: InputMap<'static, Test, TestCommand, TuiIn, TokenIter<'static>> - } - 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 { - Ok(None) - } - fn do_thing_arg (state: &mut Test, arg: usize) -> Perhaps { - Ok(None) - } - fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { - Ok(command.execute(state)?.map(|command|Self::DoSub { command })) - } - } - #[tengri_proc::command(Test)] impl TestSubcommand { - fn do_other_thing (state: &mut Test) -> Perhaps { - Ok(None) - } - fn do_other_thing_arg (state: &mut Test, arg: usize) -> Perhaps { - Ok(None) - } - } - let mut test = Test { - keys: InputMap::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'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('b'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('c'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('d'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('z'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - Ok(()) -} - -//FIXME: -//#[cfg(test)] #[test] fn test_dsl_context () { - //use crate::dsl::{Dsl, Value}; - - //struct Test; - //#[tengri_proc::expose] - //impl Test { - //fn some_bool (&self) -> bool { - //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)); -//} +#[cfg(test)] mod test; diff --git a/tengri/src/test.rs b/tengri/src/test.rs new file mode 100644 index 0000000..817c11b --- /dev/null +++ b/tengri/src/test.rs @@ -0,0 +1,97 @@ +use crate::*; + +#[test] fn test_subcommand () -> Usually<()> { + use crate::input::{Command, Handle, handle}; + //use crate::dsl::TokenIter; + use crate::tui::TuiIn; + use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; + //use crate::input::*; + //use crate::dsl::*; + struct Test { + //keys: InputMap<'static, Test, TestCommand, TuiIn, TokenIter<'static>> + } + 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 { + Ok(None) + } + fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + Ok(None) + } + fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { + Ok(command.execute(state)?.map(|command|Self::DoSub { command })) + } + } + #[tengri_proc::command(Test)] + impl TestSubcommand { + fn do_other_thing (_state: &mut Test) -> Perhaps { + Ok(None) + } + fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + Ok(None) + } + } + let mut test = Test { + keys: InputMap::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'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('b'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('c'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('d'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + kind: KeyEventKind::Press, + code: KeyCode::Char('z'), + modifiers: KeyModifiers::NONE, + state: KeyEventState::NONE, + })))?); + Ok(()) +} + +//FIXME: +//#[cfg(test)] #[test] fn test_dsl_context () { + //use crate::dsl::{Dsl, Value}; + + //struct Test; + //#[tengri_proc::expose] + //impl Test { + //fn some_bool (&self) -> bool { + //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)); +//} diff --git a/tui/examples/edn01.edn b/tui/examples/edn/edn01.edn similarity index 100% rename from tui/examples/edn01.edn rename to tui/examples/edn/edn01.edn diff --git a/tui/examples/edn02.edn b/tui/examples/edn/edn02.edn similarity index 100% rename from tui/examples/edn02.edn rename to tui/examples/edn/edn02.edn diff --git a/tui/examples/edn03.edn b/tui/examples/edn/edn03.edn similarity index 100% rename from tui/examples/edn03.edn rename to tui/examples/edn/edn03.edn diff --git a/tui/examples/edn04.edn b/tui/examples/edn/edn04.edn similarity index 100% rename from tui/examples/edn04.edn rename to tui/examples/edn/edn04.edn diff --git a/tui/examples/edn05.edn b/tui/examples/edn/edn05.edn similarity index 100% rename from tui/examples/edn05.edn rename to tui/examples/edn/edn05.edn diff --git a/tui/examples/edn06.edn b/tui/examples/edn/edn06.edn similarity index 100% rename from tui/examples/edn06.edn rename to tui/examples/edn/edn06.edn diff --git a/tui/examples/edn07.edn b/tui/examples/edn/edn07.edn similarity index 100% rename from tui/examples/edn07.edn rename to tui/examples/edn/edn07.edn diff --git a/tui/examples/edn08.edn b/tui/examples/edn/edn08.edn similarity index 100% rename from tui/examples/edn08.edn rename to tui/examples/edn/edn08.edn diff --git a/tui/examples/edn09.edn b/tui/examples/edn/edn09.edn similarity index 100% rename from tui/examples/edn09.edn rename to tui/examples/edn/edn09.edn diff --git a/tui/examples/edn10.edn b/tui/examples/edn/edn10.edn similarity index 100% rename from tui/examples/edn10.edn rename to tui/examples/edn/edn10.edn diff --git a/tui/examples/edn11.edn b/tui/examples/edn/edn11.edn similarity index 100% rename from tui/examples/edn11.edn rename to tui/examples/edn/edn11.edn diff --git a/tui/examples/edn12.edn b/tui/examples/edn/edn12.edn similarity index 100% rename from tui/examples/edn12.edn rename to tui/examples/edn/edn12.edn diff --git a/tui/examples/edn13.edn b/tui/examples/edn/edn13.edn similarity index 100% rename from tui/examples/edn13.edn rename to tui/examples/edn/edn13.edn diff --git a/tui/examples/edn14.edn b/tui/examples/edn/edn14.edn similarity index 100% rename from tui/examples/edn14.edn rename to tui/examples/edn/edn14.edn diff --git a/tui/examples/edn15.edn b/tui/examples/edn/edn15.edn similarity index 100% rename from tui/examples/edn15.edn rename to tui/examples/edn/edn15.edn diff --git a/tui/examples/edn16.edn b/tui/examples/edn/edn16.edn similarity index 100% rename from tui/examples/edn16.edn rename to tui/examples/edn/edn16.edn diff --git a/tui/examples/edn17.edn b/tui/examples/edn/edn17.edn similarity index 100% rename from tui/examples/edn17.edn rename to tui/examples/edn/edn17.edn diff --git a/tui/examples/edn99.edn b/tui/examples/edn/edn99.edn similarity index 100% rename from tui/examples/edn99.edn rename to tui/examples/edn/edn99.edn diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 066ab20..63312e8 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -1,6 +1,6 @@ use tengri::{self, Usually, Perhaps, input::*, output::*, tui::*, dsl::*}; use std::sync::{Arc, RwLock}; -use crossterm::event::{*, KeyCode::*}; +//use crossterm::event::{*, KeyCode::*}; use crate::ratatui::style::Color; fn main () -> Usually<()> { @@ -13,23 +13,23 @@ fn main () -> Usually<()> { const KEYMAP: &str = "(@left prev) (@right next)"; const EXAMPLES: &'static [&'static str] = &[ - include_str!("edn01.edn"), - include_str!("edn02.edn"), - include_str!("edn03.edn"), - include_str!("edn04.edn"), - include_str!("edn05.edn"), - include_str!("edn06.edn"), - include_str!("edn07.edn"), - include_str!("edn08.edn"), - include_str!("edn09.edn"), - include_str!("edn10.edn"), - include_str!("edn11.edn"), - include_str!("edn12.edn"), - //include_str!("edn13.edn"), - include_str!("edn14.edn"), - include_str!("edn15.edn"), - include_str!("edn16.edn"), - include_str!("edn17.edn"), + include_str!("edn/edn01.edn"), + include_str!("edn/edn02.edn"), + include_str!("edn/edn03.edn"), + include_str!("edn/edn04.edn"), + include_str!("edn/edn05.edn"), + include_str!("edn/edn06.edn"), + include_str!("edn/edn07.edn"), + include_str!("edn/edn08.edn"), + include_str!("edn/edn09.edn"), + include_str!("edn/edn10.edn"), + include_str!("edn/edn11.edn"), + include_str!("edn/edn12.edn"), + //include_str!("edn/edn13.edn"), + include_str!("edn/edn14.edn"), + include_str!("edn/edn15.edn"), + include_str!("edn/edn16.edn"), + include_str!("edn/edn17.edn"), ]; handle!(TuiIn: |self: Example, input|{ @@ -63,7 +63,7 @@ impl ExampleCommand { } } -view!(TuiOut: |self: Example|{ +content!(TuiOut: |self: Example|{ let index = self.0 + 1; let wh = self.1.wh(); let src = EXAMPLES.get(self.0).unwrap_or(&""); @@ -72,12 +72,29 @@ view!(TuiOut: |self: Example|{ 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))); self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content))) -}; { - ":title" => Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed(), - ":code" => Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed(), - ":hello" => Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed(), - ":world" => Tui::bg(Color::Rgb(100, 10, 10), "world").boxed(), - ":hello-world" => "Hello world!".boxed(), - ":map-e" => Map::east(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), - ":map-s" => Map::south(5u16, ||0..5u16, |n, i|format!("{n}")).boxed(), }); + +#[tengri_proc::view(TuiOut)] +impl Example { + pub fn title (&self) -> impl Content + use<'_> { + Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed() + } + pub fn code (&self) -> impl Content + use<'_> { + Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed() + } + pub fn hello (&self) -> impl Content + use<'_> { + Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() + } + pub fn world (&self) -> impl Content + use<'_> { + Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() + } + pub fn hello_world (&self) -> impl Content + use<'_> { + "Hello world!".boxed() + } + pub fn map_e (&self) -> impl Content + use<'_> { + Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + } + pub fn map_s (&self) -> impl Content + use<'_> { + Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + } +} diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 79648b9..e3792f1 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -36,7 +36,7 @@ pub(crate) use std::io::{stdout, Stdout}; #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { use crate::*; - use std::sync::{Arc, RwLock}; + //use std::sync::{Arc, RwLock}; struct TestComponent(String); impl Content for TestComponent { fn content (&self) -> impl Render { @@ -44,21 +44,21 @@ pub(crate) use std::io::{stdout, Stdout}; } } impl Handle for TestComponent { - fn handle (&mut self, from: &TuiIn) -> Perhaps { + fn handle (&mut self, _from: &TuiIn) -> Perhaps { Ok(None) } } let engine = Tui::new()?; engine.read().unwrap().exited.store(true, std::sync::atomic::Ordering::Relaxed); let state = TestComponent("hello world".into()); - let state = std::sync::Arc::new(std::sync::RwLock::new(state)); + let _state = std::sync::Arc::new(std::sync::RwLock::new(state)); //engine.run(&state)?; Ok(()) } #[cfg(test)] #[test] fn test_parse_key () { - use KeyModifiers as Mods; - let test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); + //use KeyModifiers as Mods; + let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); //test(":x", //KeyEvent::new(KeyCode::Char('x'), Mods::NONE)); //test(":ctrl-x", diff --git a/tui/src/tui_content/tui_field.rs b/tui/src/tui_content/tui_field.rs index a60cc32..3e47e9f 100644 --- a/tui/src/tui_content/tui_field.rs +++ b/tui/src/tui_content/tui_field.rs @@ -60,7 +60,7 @@ impl Field { value_align: None, } } - fn label ( + pub fn label ( self, label: Option, align: Option, @@ -75,7 +75,7 @@ impl Field { ..self } } - fn value ( + pub fn value ( self, value: Option, align: Option, diff --git a/tui/src/tui_content/tui_number.rs b/tui/src/tui_content/tui_number.rs index 8e283ce..46c4ef1 100644 --- a/tui/src/tui_content/tui_number.rs +++ b/tui/src/tui_content/tui_number.rs @@ -1,5 +1,5 @@ use crate::*; -render!(TuiOut: |self: u64, to|todo!()); +render!(TuiOut: |self: u64, _to|todo!()); -render!(TuiOut: |self: f64, to|todo!()); +render!(TuiOut: |self: f64, _to|todo!()); diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index c9cd788..3bf7791 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -56,13 +56,13 @@ impl TuiIn { } } -#[cfg(feature = "dsl")] -impl DslInput for TuiIn { - fn matches_dsl (&self, token: &str) -> bool { - if let Some(event) = KeyMatcher::new(token).build() { - &event == self.event() - } else { - false - } - } -} +//#[cfg(feature = "dsl")] +//impl DslInput for TuiIn { + //fn matches_dsl (&self, token: &str) -> bool { + //if let Some(event) = KeyMatcher::new(token).build() { + //&event == self.event() + //} else { + //false + //} + //} +//} From c8827b43c38bb4f14dfcdecdfe8324af18cf89f5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 13 Jun 2025 11:42:20 +0300 Subject: [PATCH 110/178] wip: slowly remembering where i was --- core/src/lib.rs | 37 ++++++++++++++++--- dsl/src/dsl_display.rs | 41 ++++++++++----------- dsl/src/dsl_domain.rs | 84 ------------------------------------------ dsl/src/dsl_value.rs | 38 +++++++++++++++++-- dsl/src/lib.rs | 2 +- input/src/input_dsl.rs | 13 ++++--- output/src/ops_dsl.rs | 18 +++++---- proc/src/proc_view.rs | 4 +- tengri/src/test.rs | 12 +++--- 9 files changed, 114 insertions(+), 135 deletions(-) delete mode 100644 dsl/src/dsl_domain.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index b512ae9..39d7e6c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,4 @@ -use std::error::Error; +pub(crate) use std::error::Error; /// Standard result type. pub type Usually = Result>; @@ -16,29 +16,56 @@ pub type Perhaps = Result, Box>; } pub trait Has: Send + Sync { - fn get (&self) -> &T; + fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; } #[macro_export] macro_rules! has { ($T:ty: |$self:ident : $S:ty| $x:expr) => { impl Has<$T> for $S { - fn get (&$self) -> &$T { &$x } + fn get (&$self) -> &$T { &$x } fn get_mut (&mut $self) -> &mut $T { &mut $x } } }; } pub trait MaybeHas: Send + Sync { - fn get (&self) -> Option<&T>; + fn get (&self) -> Option<&T>; fn get_mut (&mut self) -> Option<&mut T>; } #[macro_export] macro_rules! maybe_has { ($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => { impl MaybeHas<$T> for $S { - fn get (&$self) -> Option<&$T> $x + fn get (&$self) -> Option<&$T> $x fn get_mut (&mut $self) -> Option<&mut $T> $y } }; } + +/// 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; + /// Invoke a custom operation, converting a `None` result to a custom `Box`. + fn eval >> (&self, args: Args, error: impl Fn()->E) + -> Usually + { + match self.try_eval(args)? { + Some(value) => Ok(value), + _ => Result::Err(format!("Eval: {}", error().into()).into()) + } + } +} + +//impl, I, O> Eval for &S { + //fn try_eval (&self, input: I) -> Perhaps { + //(*self).try_eval(input) + //} +//} + +//impl, I: Ast, O: Dsl> Eval for S { + //fn try_eval (&self, input: I) -> Perhaps { + //Dsl::try_provide(self, input) + //} +//} diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs index f86b4be..ab190b7 100644 --- a/dsl/src/dsl_display.rs +++ b/dsl/src/dsl_display.rs @@ -1,30 +1,29 @@ use crate::*; -use std::fmt::{Display, Formatter, Error as FormatError}; + impl Display for Ast { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { - use Value::*; - write!(out, "{}", match &self.0 { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) + match &self.0 { + Value::Nil => Ok(()), + Value::Err(e) => write!(out, "[error: {e}]"), + Value::Num(n) => write!(out, "{n}"), + Value::Sym(s) => write!(out, "{s}"), + Value::Key(s) => write!(out, "{s}"), + Value::Str(s) => write!(out, "{s}"), + Value::Exp(_, e) => write!(out, "{e:?}"), + } } } + impl<'source> Display for CstValue<'source> { fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { - use Value::*; - write!(out, "{}", match self { - Nil => String::new(), - Err(e) => format!("[error: {e}]"), - Num(n) => format!("{n}"), - Sym(s) => format!("{s}"), - Key(s) => format!("{s}"), - Str(s) => format!("{s}"), - Exp(_, e) => format!("{e:?}"), - }) + match self { + Value::Nil => Ok(()), + Value::Err(e) => write!(out, "[error: {e}]"), + Value::Num(n) => write!(out, "{n}"), + Value::Sym(s) => write!(out, "{s}"), + Value::Key(s) => write!(out, "{s}"), + Value::Str(s) => write!(out, "{s}"), + Value::Exp(_, e) => write!(out, "{e:?}"), + } } } diff --git a/dsl/src/dsl_domain.rs b/dsl/src/dsl_domain.rs deleted file mode 100644 index d8d502e..0000000 --- a/dsl/src/dsl_domain.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::*; - -pub trait Eval { - fn try_eval (&self, input: Input) -> Perhaps; - fn eval >, F: Fn()->E> ( - &self, input: Input, error: F - ) -> Usually { - if let Some(value) = self.try_eval(input)? { - Ok(value) - } else { - Result::Err(format!("Eval: {}", error().into()).into()) - } - } -} - -//impl, I, O> Eval for &S { - //fn try_eval (&self, input: I) -> Perhaps { - //(*self).try_eval(input) - //} -//} - -//impl, I: Ast, O: Dsl> Eval for S { - //fn try_eval (&self, input: I) -> Perhaps { - //Dsl::try_provide(self, input) - //} -//} - -/// May construct [Self] from token stream. -pub trait Dsl: Sized { - fn try_provide (state: &State, source: Ast) -> Perhaps; - fn provide >, F: Fn()->E> ( - state: &State, source: Ast, error: F - ) -> Usually { - let next = format!("{source:?}"); - if let Some(value) = Self::try_provide(state, source)? { - Ok(value) - } else { - Result::Err(format!("Dsl: {}: {next:?}", error().into()).into()) - } - } -} - -//pub trait Give<'state, 'source, Type> { - ///// Implement this to be able to [Give] [Type] from the [Cst]. - ///// Advance the stream if returning `Ok>`. - //fn give (&'state self, words: Cst<'source>) -> Perhaps; - ///// Return custom error on [None]. - //fn give_or_fail >, F: Fn()->E> ( - //&'state self, mut words: Cst<'source>, error: F - //) -> Usually { - //let next = words.peek().map(|x|x.value).clone(); - //if let Some(value) = Give::::give(self, words)? { - //Ok(value) - //} else { - //Result::Err(format!("give: {}: {next:?}", error().into()).into()) - //} - //} -//} -//#[macro_export] macro_rules! give { - //($Type:ty|$state:ident:$State:ident,$words:ident|$expr:expr) => { - //impl Give<$Type> for $State { - //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { - //let $state = self; - //$expr - //} - //} - //}; - //($Type:path$(,$Arg:ident)*|$state:ident,$words:ident|$expr:expr) => { - //impl)++ $(, $Arg)*> Give<$Type> for State { - //fn give (&self, mut $words: Cst) -> Perhaps<$Type> { - //let $state = self; - //$expr - //} - //} - //} -//} -/////// Implement the [Give] trait, which boils down to -/////// specifying two types and providing an expression. -//#[macro_export] macro_rules! from_dsl { - //($Type:ty: |$state:ident:$State:ty, $words:ident|$expr:expr) => { - //give! { $Type|$state:$State,$words|$expr } - //}; -//} - diff --git a/dsl/src/dsl_value.rs b/dsl/src/dsl_value.rs index f06cf84..25d7ec5 100644 --- a/dsl/src/dsl_value.rs +++ b/dsl/src/dsl_value.rs @@ -1,5 +1,20 @@ use crate::*; -use std::fmt::Display; + +/// Thing that may construct itself from state and [DslValue]. +pub trait Dsl: Sized { + fn try_provide (state: &State, value: impl DslValue) -> Perhaps; + fn provide ( + state: &State, + value: impl DslValue, + error: impl Fn()->Box + ) -> Usually { + match Self::try_provide(state, value)? { + Some(value) => Ok(value), + _ => Err(error()) + } + } +} + pub trait DslValue: PartialEq + Clone + Default + Debug { type Str: AsRef + PartialEq + Clone + Default + Debug; type Exp: PartialEq + Clone + Default + Debug; @@ -29,6 +44,7 @@ pub trait DslValue: PartialEq + Clone + Default + Debug { fn exp_head (&self) -> Option<&Self> { None } // TODO fn exp_tail (&self) -> Option<&[Self]> { None } // TODO } + impl< Str: AsRef + PartialEq + Clone + Default + Debug, Exp: PartialEq + Clone + Default + Debug, @@ -40,8 +56,20 @@ impl< } } -pub enum Value { Nil, Err(DslError), Num(usize), Sym(S), Key(S), Str(S), Exp(usize, X), } -impl Default for Value { fn default () -> Self { Self:: Nil } } +pub enum Value { + Nil, + Err(DslError), + Num(usize), + Sym(S), + Key(S), + Str(S), + Exp(usize, X), +} + +impl Default for Value { + fn default () -> Self { Self:: Nil } +} + impl PartialEq for Value { fn eq (&self, other: &Self) -> bool { use Value::*; @@ -57,6 +85,7 @@ impl PartialEq for Value { } } } + impl Clone for Value { fn clone (&self) -> Self { use Value::*; @@ -71,12 +100,15 @@ impl Clone for Value { } } } + impl Copy for Value {} + impl Debug for Value { fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { todo!() } } + impl Display for Value { fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { todo!() diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 24a5ee0..485f09e 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -36,6 +36,7 @@ #![feature(impl_trait_in_fn_trait_return)] pub(crate) use ::tengri_core::*; pub(crate) use std::fmt::Debug; +pub(crate) use std::fmt::{Display, Formatter, Error as FormatError}; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; @@ -43,7 +44,6 @@ pub(crate) use self::DslError::*; mod dsl_ast; pub use self::dsl_ast::*; mod dsl_cst; pub use self::dsl_cst::*; mod dsl_display; //pub use self::dsl_display::*; -mod dsl_domain; pub use self::dsl_domain::*; mod dsl_error; pub use self::dsl_error::*; mod dsl_iter; pub use self::dsl_iter::*; mod dsl_token; pub use self::dsl_token::*; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index d34a8f7..76f3c3b 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,7 +4,10 @@ use std::fmt::Debug; #[derive(Default, Debug)] pub struct InputLayers(Vec); -#[derive(Default, Debug)] pub struct InputLayer(Option, Ast); +#[derive(Default, Debug)] pub struct InputLayer { + condition: Option, + bindings: Ast, +} impl InputLayers { pub fn new (layer: Ast) -> Self { @@ -19,8 +22,8 @@ impl InputLayers { pub fn add_layer (&mut self, layer: Ast) -> &mut Self { self.add_layer_if(None, layer.into()); self } - pub fn add_layer_if (&mut self, condition: Option, binding: Ast) -> &mut Self { - self.0.push(InputLayer(condition, binding)); + pub fn add_layer_if (&mut self, condition: Option, bindings: Ast) -> &mut Self { + self.0.push(InputLayer { condition, bindings }); self } pub fn handle + Eval, I: Eval, O: Command> (&self, state: &mut S, input: I) -> Perhaps { @@ -33,13 +36,13 @@ pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); impl<'a, S: Eval + Eval, I: Eval, O: Command> Eval for InputHandle<'a, S> { fn try_eval (&self, input: I) -> Perhaps { let Self(state, layers) = self; - for InputLayer(condition, binding) in layers.iter() { + for InputLayer { condition, bindings } in layers.iter() { let mut matches = true; if let Some(condition) = condition { matches = state.eval(condition.clone(), ||"input: no condition")?; } if matches - && let Some(exp) = binding.exp() + && let Some(exp) = bindings.exp() && let Some(ast) = exp.peek() && input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? && let Some(command) = state.try_eval(ast)? { diff --git a/output/src/ops_dsl.rs b/output/src/ops_dsl.rs index 5d131bb..94ded3f 100644 --- a/output/src/ops_dsl.rs +++ b/output/src/ops_dsl.rs @@ -2,8 +2,9 @@ use crate::*; use Value::*; impl Dsl for When where S: Eval + Eval { - fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, mut exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { + fn try_provide (state: &S, source: impl DslValue) -> Perhaps { + if let Exp(_, mut exp) = source.value() + && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { let _ = exp.next(); return Ok(Some(Self( state.eval(exp.next().unwrap(), ||"when: expected condition")?, @@ -15,8 +16,9 @@ impl Dsl for When where S: Eval + Eval { } impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, mut exp) = source.0 && let Some(Ast(Key(id))) = exp.peek() && *id == *"either" { + fn try_provide (state: &S, source: impl DslValue) -> Perhaps { + 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: expected condition")?, @@ -29,8 +31,8 @@ impl Dsl for Either where S: Eval + Eval + } impl Dsl for Align where S: Eval, A> { - fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, source) = source.0 { + fn try_provide (state: &S, source: impl DslValue) -> Perhaps { + 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: expected content")?), @@ -52,8 +54,8 @@ impl Dsl for Align where S: Eval, A> { } impl Dsl for Bsp where S: Eval, A> + Eval, B> { - fn try_provide (state: &S, source: Ast) -> Perhaps { - if let Exp(_, exp) = source.0 { + fn try_provide (state: &S, source: impl DslValue) -> Perhaps { + 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( diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index ef5aa49..75876d0 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -64,7 +64,9 @@ impl ToTokens for ViewDef { /// 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>> { + fn try_provide (state: &'state #self_ty, mut words: ::tengri::dsl::Ast) -> + Perhaps + 'state>> + { Ok(if let Some(::tengri::dsl::Token { value, .. }) = words.peek() { match value { #(#builtin)* #(#exposed)* _ => None } } else { diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 817c11b..839baee 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,14 +1,12 @@ use crate::*; #[test] fn test_subcommand () -> Usually<()> { - use crate::input::{Command, Handle, handle}; - //use crate::dsl::TokenIter; use crate::tui::TuiIn; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; - //use crate::input::*; - //use crate::dsl::*; + use crate::input::*; + use crate::dsl::*; struct Test { - //keys: InputMap<'static, Test, TestCommand, TuiIn, TokenIter<'static>> + keys: InputLayers } handle!(TuiIn: |self: Test, input|if let Some(command) = self.keys.command(self, input) { Ok(Some(true)) @@ -37,12 +35,12 @@ use crate::*; } } let mut test = Test { - keys: InputMap::new(" + keys: InputLayers::new(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()) + ".into())) }; assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { kind: KeyEventKind::Press, From 91dc77cfea39b2344dd7cf0e8e2a26104d06c712 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 14 Jun 2025 16:08:02 +0300 Subject: [PATCH 111/178] wip: refactor dsl --- dsl/src/ast.rs | 31 ++++ dsl/src/cst.rs | 290 ++++++++++++++++++++++++++++++++++++ dsl/src/dsl.rs | 110 ++++++++++++++ dsl/src/dsl_ast.rs | 45 ------ dsl/src/dsl_cst.rs | 183 ----------------------- dsl/src/dsl_display.rs | 29 ---- dsl/src/dsl_error.rs | 16 -- dsl/src/dsl_iter.rs | 130 ---------------- dsl/src/dsl_test.rs | 111 -------------- dsl/src/dsl_token.rs | 25 ---- dsl/src/dsl_value.rs | 116 --------------- dsl/src/lib.rs | 121 +++++++++++++-- input/src/input_dsl.rs | 46 +++--- output/src/lib.rs | 183 +++++++++++++++++++++-- output/src/ops/transform.rs | 192 ++++++++++++++++++++++++ output/src/ops_dsl.rs | 126 ---------------- 16 files changed, 931 insertions(+), 823 deletions(-) create mode 100644 dsl/src/ast.rs create mode 100644 dsl/src/cst.rs create mode 100644 dsl/src/dsl.rs delete mode 100644 dsl/src/dsl_ast.rs delete mode 100644 dsl/src/dsl_cst.rs delete mode 100644 dsl/src/dsl_display.rs delete mode 100644 dsl/src/dsl_error.rs delete mode 100644 dsl/src/dsl_iter.rs delete mode 100644 dsl/src/dsl_test.rs delete mode 100644 dsl/src/dsl_token.rs delete mode 100644 dsl/src/dsl_value.rs delete mode 100644 output/src/ops_dsl.rs diff --git a/dsl/src/ast.rs b/dsl/src/ast.rs new file mode 100644 index 0000000..be188f0 --- /dev/null +++ b/dsl/src/ast.rs @@ -0,0 +1,31 @@ +use crate::*; + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(pub AstValue); + +/// 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<'src> From> for Ast { + fn from (token: Cst<'src>) -> Self { + token.value().into() + } +} + +impl<'src> From> for Ast { + fn from (value: CstValue<'src>) -> Self { + 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()), + }) + } +} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs new file mode 100644 index 0000000..3dff71f --- /dev/null +++ b/dsl/src/cst.rs @@ -0,0 +1,290 @@ +//! The concrete syntax tree (CST) implements zero-copy +//! parsing of the DSL from a string reference. CST items +//! preserve info about their location in the source. + +use crate::*; + +/// Implement the const iterator pattern. +macro_rules! const_iter { + ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { + impl$(<$l>)? Iterator for $Struct { + type Item = $Item; + fn next (&mut $self) -> Option<$Item> { $expr } + } + impl$(<$l>)? ConstIntoIter for $Struct { + type Kind = IsIteratorKind; + type Item = $Item; + type IntoIter = Self; + } + } +} + +/// Static iteration helper used by [cst]. +macro_rules! iterate { + ($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; + while let Some(($arg, next)) = iter.next() { + $body; + iter = next; + } + } +} + +/// CST stores strings as source references and expressions as [SourceIter] instances. +pub type CstValue<'source> = DslValue<&'source str, SourceIter<'source>>; + +/// Token sharing memory with source reference. +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct Cst<'src> { + /// Reference to source text. + pub source: &'src str, + /// Index of 1st character of token. + pub start: usize, + /// Length of token. + pub length: usize, + /// Meaning of token. + pub value: CstValue<'src>, +} + +impl<'src> Cst<'src> { + pub const fn new ( + source: &'src str, + start: usize, + length: usize, + value: CstValue<'src> + ) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'src self) -> &'src str { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'src self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'src self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: CstValue<'src>) -> Self { + Self { value, ..self } + } + 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 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() }, + } + } + pub const fn grow_key (self) -> Self { + let token = self.grow(); + token.with_value(DslValue::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))) + } + pub const fn grow_str (self) -> Self { + let token = self.grow(); + token.with_value(DslValue::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)))) + } 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)) + } else { + unreachable!() + } + } + pub const fn grow_out (self) -> Self { + let token = self.grow_exp(); + if let DslValue::Exp(depth, source) = token.value() { + if depth > 0 { + token.with_value(DslValue::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) +} + +pub const fn peek_src <'src> (source: &'src str) -> Option> { + use DslValue::*; + 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), + Nil => match c { + ' '|'\n'|'\r'|'\t' => + token.grow(), + '(' => + Cst::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), + '"' => + Cst::new(source, start, 1, Str(str_range(source, start, start + 1))), + ':'|'@' => + Cst::new(source, start, 1, Sym(str_range(source, start, start + 1))), + '/'|'a'..='z' => + 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) + }), + _ => token.error(Unexpected(c)) + }, + Str(_) => match c { + '"' => return Some(token), + _ => token.grow_str(), + }, + Num(n) => match c { + '0'..='9' => token.grow_num(n, c), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Sym(_) => match c { + 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Key(_) => match c { + 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), + ' '|'\n'|'\r'|'\t'|')' => return Some(token), + _ => token.error(Unexpected(c)) + }, + Exp(depth, _) => match depth { + 0 => return Some(token.grow_exp()), + _ => match c { + ')' => token.grow_out(), + '(' => token.grow_in(), + _ => token.grow_exp(), + } + }, + }); + match token.value() { + Nil => None, + _ => Some(token), + } +} + +/// Owns a reference to the source text. +/// [SourceConstIter::next] emits subsequent pairs of: +/// * a [Cst] and +/// * the source text remaining +/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceConstIter<'src>(pub &'src str); + +impl<'src> From> for SourceIter<'src> { + fn from (source: SourceConstIter<'src>) -> Self{ + Self(source) + } +} + +impl<'src> From<&'src str> for SourceConstIter<'src> { + fn from (source: &'src str) -> Self{ + Self::new(source) + } +} + +impl<'src> SourceConstIter<'src> { + pub const fn new (source: &'src str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(Cst<'src>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(Cst<'src>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + +const_iter!(<'src>|self: SourceConstIter<'src>| => Cst<'src> => self.next_mut().map(|(result, _)|result)); + +/// Provides a native [Iterator] API over [SourceConstIter], +/// emitting [Cst] items. +/// +/// [Cst::next] returns just the [Cst] and mutates `self`, +/// instead of returning an updated version of the struct as [SourceConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct SourceIter<'src>(pub SourceConstIter<'src>); + +impl<'src> SourceIter<'src> { + pub const fn new (source: &'src str) -> Self { + Self(SourceConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} + +impl<'src> Iterator for SourceIter<'src> { + type Item = Cst<'src>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{ + self.0 = rest; + item + }) + } +} + +impl<'src> From<&'src str> for SourceIter<'src> { + fn from (source: &'src str) -> Self{ + Self(SourceConstIter(source)) + } +} + +impl<'src> Into>> for SourceIter<'src> { + fn into (self) -> Vec> { + self.collect() + } +} + +impl<'src> Into> for SourceIter<'src> { + fn into (self) -> Vec { + self.map(Into::into).collect() + } +} diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs new file mode 100644 index 0000000..615804d --- /dev/null +++ b/dsl/src/dsl.rs @@ -0,0 +1,110 @@ +use crate::*; + +#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslError { + #[error("parse failed: not implemented")] + Unimplemented, + #[error("parse failed: empty")] + Empty, + #[error("parse failed: incomplete")] + Incomplete, + #[error("parse failed: unexpected character '{0}'")] + Unexpected(char), + #[error("parse failed: error #{0}")] + 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]. +#[derive(Clone, Debug, PartialEq, Default)] +pub enum DslValue { + #[default] + Nil, + Err(DslError), + Num(usize), + Sym(Str), + Key(Str), + Str(Str), + Exp(usize, Exp), +} + +impl DslValue { + pub fn is_nil (&self) -> bool { + matches!(self, Self::Nil) + } + pub fn as_err (&self) -> Option<&DslError> { + 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!() + } + pub fn exp_head (&self) -> Option<&Self> { + todo!() + } // TODO + pub fn exp_tail (&self) -> Option<&Exp> { + todo!() + } // TODO + pub fn peek (&self) -> Option { + todo!() + } + pub fn next (&mut self) -> Option { + todo!() + } + pub fn rest (self) -> Vec { + todo!() + } +} + +impl Copy for DslValue {} diff --git a/dsl/src/dsl_ast.rs b/dsl/src/dsl_ast.rs deleted file mode 100644 index c28d73a..0000000 --- a/dsl/src/dsl_ast.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::*; -use std::sync::Arc; - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub Value, AstIter>); - -impl<'source> From> for Ast { - fn from (token: CstToken<'source>) -> Self { - token.value().into() - } -} - -impl<'source> From> for Ast { - fn from (value: CstValue<'source>) -> Self { - use Value::*; - Self(match value { - 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.into()), - }) - } -} - -impl DslValue for Ast { - type Str = Arc; - type Exp = AstIter; - fn value (&self) -> &Value, AstIter> { - self.0.value() - } -} - -impl DslToken for Ast { - type Value = Self; - type Meta = (); - fn value (&self) -> &Self::Value { - self - } - fn meta (&self) -> &Self::Meta { - &() - } -} diff --git a/dsl/src/dsl_cst.rs b/dsl/src/dsl_cst.rs deleted file mode 100644 index cfae7e2..0000000 --- a/dsl/src/dsl_cst.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::*; - -/// CST stores strings as source references and expressions as new [SourceIter] instances. -pub type CstValue<'source> = Value<&'source str, SourceIter<'source>>; - -/// Token sharing memory with source reference. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstToken<'source>(pub CstValue<'source>, pub CstMeta<'source>); - -/// Reference to the source slice. -#[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct CstMeta<'source> { - pub source: &'source str, - pub start: usize, - pub length: usize, -} - -impl<'source> CstToken<'source> { - pub const fn new ( - source: &'source str, start: usize, length: usize, value: CstValue<'source> - ) -> Self { - Self(value, CstMeta { source, start, length }) - } - pub const fn end (&self) -> usize { - self.1.start.saturating_add(self.1.length) - } - pub const fn slice (&'source self) -> &'source str { - self.slice_source(self.1.source) - } - pub const fn slice_source <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.1.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'source self, source: &'range str) -> &'range str { - str_range(source, self.1.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: CstValue<'source>) -> Self { - Self(value, self.1) - } - pub const fn value (&self) -> CstValue<'source> { - self.0 - } - pub const fn error (self, error: DslError) -> Self { - Self(Value::Err(error), self.1) - } - pub const fn grow (self) -> Self { - Self(self.0, CstMeta { length: self.1.length.saturating_add(1), ..self.1 }) - } - pub const fn grow_num (self, m: usize, c: char) -> Self { - use Value::*; - match to_digit(c) { - Result::Ok(n) => Self(Num(10*m+n), self.grow().1), - Result::Err(e) => Self(Err(e), self.grow().1), - } - } - pub const fn grow_key (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Key(token.slice_source(self.1.source))) - } - pub const fn grow_sym (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Sym(token.slice_source(self.1.source))) - } - pub const fn grow_str (self) -> Self { - use Value::*; - let token = self.grow(); - token.with_value(Str(token.slice_source(self.1.source))) - } - pub const fn grow_exp (self) -> Self { - use Value::*; - let token = self.grow(); - if let Exp(depth, _) = token.value() { - token.with_value(Exp(depth, SourceIter::new(token.slice_source_exp(self.1.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value() { - token.with_value(Value::Exp(depth.saturating_add(1), source)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let Value::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(Value::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} - -/// Static iteration helper. -#[macro_export] macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'a> (source: &'a str) -> Option> { - use Value::*; - let mut token: CstToken<'a> = CstToken::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - CstToken::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), - '"' => - CstToken::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstToken::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstToken::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstToken::new(source, start, 1, match to_digit(c) { - Ok(c) => Value::Num(c), - Result::Err(e) => Value::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} diff --git a/dsl/src/dsl_display.rs b/dsl/src/dsl_display.rs deleted file mode 100644 index ab190b7..0000000 --- a/dsl/src/dsl_display.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::*; - -impl Display for Ast { - fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { - match &self.0 { - Value::Nil => Ok(()), - Value::Err(e) => write!(out, "[error: {e}]"), - Value::Num(n) => write!(out, "{n}"), - Value::Sym(s) => write!(out, "{s}"), - Value::Key(s) => write!(out, "{s}"), - Value::Str(s) => write!(out, "{s}"), - Value::Exp(_, e) => write!(out, "{e:?}"), - } - } -} - -impl<'source> Display for CstValue<'source> { - fn fmt (&self, out: &mut Formatter) -> Result<(), FormatError> { - match self { - Value::Nil => Ok(()), - Value::Err(e) => write!(out, "[error: {e}]"), - Value::Num(n) => write!(out, "{n}"), - Value::Sym(s) => write!(out, "{s}"), - Value::Key(s) => write!(out, "{s}"), - Value::Str(s) => write!(out, "{s}"), - Value::Exp(_, e) => write!(out, "{e:?}"), - } - } -} diff --git a/dsl/src/dsl_error.rs b/dsl/src/dsl_error.rs deleted file mode 100644 index ffb53fd..0000000 --- a/dsl/src/dsl_error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::*; - -pub type DslResult = Result; - -#[derive(Error, Debug, Copy, Clone, PartialEq)] pub enum DslError { - #[error("parse failed: not implemented")] - Unimplemented, - #[error("parse failed: empty")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), -} diff --git a/dsl/src/dsl_iter.rs b/dsl/src/dsl_iter.rs deleted file mode 100644 index 2e0e47a..0000000 --- a/dsl/src/dsl_iter.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::*; - -pub trait DslIter { - type Token: DslToken; - fn peek (&self) -> Option<::Value>; - fn next (&mut self) -> Option<::Value>; - fn rest (self) -> Vec; -} - -/// Implement the const iterator pattern. -#[macro_export] macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -/// Owns a reference to the source text. -/// [SourceConstIter::next] emits subsequent pairs of: -/// * a [CstToken] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceConstIter<'source>(pub &'source str); - -impl<'source> From> for SourceIter<'source> { - fn from (source: SourceConstIter<'source>) -> Self{ - Self(source) - } -} - -/// Provides a native [Iterator] API over [SourceConstIter], -/// emitting [CstToken] items. -/// -/// [Cst::next] returns just the [CstToken] and mutates `self`, -/// instead of returning an updated version of the struct as [SourceConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'source>(pub SourceConstIter<'source>); - -impl<'source> SourceIter<'source> { - pub const fn new (source: &'source str) -> Self { - Self(SourceConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'source> Iterator for SourceIter<'source> { - type Item = CstToken<'source>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{self.0 = rest; item}) - } -} - -impl<'source> From<&'source str> for SourceIter<'source> { - fn from (source: &'source str) -> Self{ - Self(SourceConstIter(source)) - } -} - -impl<'source> Into>> for SourceIter<'source> { - fn into (self) -> Vec> { - self.collect() - } -} - -impl<'source> Into> for SourceIter<'source> { - fn into (self) -> Vec { - self.map(Into::into).collect() - } -} - -impl<'source> From<&'source str> for SourceConstIter<'source> { - fn from (source: &'source str) -> Self{ - Self::new(source) - } -} - -impl<'source> SourceConstIter<'source> { - pub const fn new (source: &'source str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(CstToken<'source>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(CstToken<'source>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -const_iter!(<'source>|self: SourceConstIter<'source>| => CstToken<'source> => self.next_mut().map(|(result, _)|result)); - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct AstIter(std::collections::VecDeque); - -impl DslIter for AstIter { - type Token = Ast; - fn peek (&self) -> Option { - self.0.get(0).cloned() - } - fn next (&mut self) -> Option { - self.0.pop_front() - } - fn rest (self) -> Vec { - self.0.into() - } -} - -impl<'source> From> for AstIter { - fn from (source: SourceIter<'source>) -> Self { - Self(source.map(Into::into).collect()) - } -} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs deleted file mode 100644 index 2f5ce15..0000000 --- a/dsl/src/dsl_test.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::*; - -#[cfg(test)] mod test_token_iter { - use crate::*; - //use proptest::prelude::*; - #[test] fn test_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] const fn test_const_iters () { - let iter = crate::SourceConstIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] fn test_num () { - let _digit = to_digit('0'); - let _digit = to_digit('x'); - let _number = to_number(&"123"); - let _number = to_number(&"12asdf3"); - } - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::SourceIter::new(&source); - ////let _ = iter.next(); - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} -} - -//#[cfg(test)] mod test_token_prop { - //use crate::{CstToken, CstMeta, Value::*}; - //use proptest::prelude::*; - //proptest! { - //#[test] fn test_token_prop ( - //source in "\\PC*", - //start in usize::MIN..usize::MAX, - //length in usize::MIN..usize::MAX, - //) { - //let token = CstToken(Nil, CstMeta { source: &source, start, length }); - //let _ = token.slice(); - //} - //} -//} - -#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { - use crate::Value::*; - let source = ":f00"; - let mut token = CstToken(Sym(":"), CstMeta { source, start: 0, length: 1 }); - token = token.grow_sym(); - assert_eq!(token, CstToken(Sym(":f"), CstMeta { source, start: 0, length: 2, })); - token = token.grow_sym(); - assert_eq!(token, CstToken(Sym(":f0"), CstMeta { source, start: 0, length: 3, })); - token = token.grow_sym(); - assert_eq!(token, CstToken(Sym(":f00"), CstMeta { source, start: 0, length: 4, })); - - assert_eq!(None, - SourceIter::new("").next()); - assert_eq!(None, - SourceIter::new(" \n \r \t ").next()); - assert_eq!(&Num(7), - SourceIter::new("7").next().unwrap().0.value()); - assert_eq!(&Num(100), - SourceIter::new(" 100 ").next().unwrap().0.value()); - assert_eq!(&Err(Unexpected('a')), - SourceIter::new(" 9a ").next().unwrap().0.value()); - assert_eq!(&Sym(":123foo"), - SourceIter::new(" :123foo ").next().unwrap().0.value()); - assert_eq!(&Sym("@bar456"), - SourceIter::new(" \r\r\r\n\n\n@bar456\t\t\t\t\t\t").next().unwrap().0.value()); - assert_eq!(&Key("foo123"), - SourceIter::new("foo123").next().unwrap().0.value()); - assert_eq!(&Key("foo/bar"), - SourceIter::new("foo/bar").next().unwrap().0.value()); - assert_eq!(&Str("foo/bar"), - SourceIter::new("\"foo/bar\"").next().unwrap().0.value()); - assert_eq!(&Str("foo/bar"), - SourceIter::new(" \"foo/bar\" ").next().unwrap().0.value()); - - Ok(()) -} - -//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { - //// 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. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter::new(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} diff --git a/dsl/src/dsl_token.rs b/dsl/src/dsl_token.rs deleted file mode 100644 index 4411e7e..0000000 --- a/dsl/src/dsl_token.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::*; - -#[derive(PartialEq, Clone, Default, Debug)] -pub struct Token< - V: PartialEq + Clone + Default + Debug, - M: PartialEq + Clone + Default + Debug, ->(pub V, pub M); - -pub trait DslToken: PartialEq + Clone + Default + Debug { - type Value: DslValue; - type Meta: Clone + Default + Debug; - fn value (&self) -> &Self::Value; - fn meta (&self) -> &Self::Meta; -} - -impl DslToken for Token { - type Value = V; - type Meta = M; - fn value (&self) -> &Self::Value { - &self.0 - } - fn meta (&self) -> &Self::Meta { - &self.1 - } -} diff --git a/dsl/src/dsl_value.rs b/dsl/src/dsl_value.rs deleted file mode 100644 index 25d7ec5..0000000 --- a/dsl/src/dsl_value.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::*; - -/// Thing that may construct itself from state and [DslValue]. -pub trait Dsl: Sized { - fn try_provide (state: &State, value: impl DslValue) -> Perhaps; - fn provide ( - state: &State, - value: impl DslValue, - error: impl Fn()->Box - ) -> Usually { - match Self::try_provide(state, value)? { - Some(value) => Ok(value), - _ => Err(error()) - } - } -} - -pub trait DslValue: PartialEq + Clone + Default + Debug { - type Str: AsRef + PartialEq + Clone + Default + Debug; - type Exp: PartialEq + Clone + Default + Debug; - fn value (&self) -> &Value; - fn nil (&self) -> bool { - matches!(self.value(), Value::Nil) - } - fn err (&self) -> Option<&DslError> { - if let Value::Err(e) = self.value() { Some(e) } else { None } - } - fn num (&self) -> Option { - if let Value::Num(n) = self.value() { Some(*n) } else { None } - } - fn sym (&self) -> Option<&str> { - if let Value::Sym(s) = self.value() { Some(s.as_ref()) } else { None } - } - fn key (&self) -> Option<&str> { - if let Value::Key(k) = self.value() { Some(k.as_ref()) } else { None } - } - fn str (&self) -> Option<&str> { - if let Value::Str(s) = self.value() { Some(s.as_ref()) } else { None } - } - fn exp (&self) -> Option<&Self::Exp> { - if let Value::Exp(_, x) = self.value() { Some(x) } else { None } - } - fn exp_depth (&self) -> Option { None } // TODO - fn exp_head (&self) -> Option<&Self> { None } // TODO - fn exp_tail (&self) -> Option<&[Self]> { None } // TODO -} - -impl< - Str: AsRef + PartialEq + Clone + Default + Debug, - Exp: PartialEq + Clone + Default + Debug, -> DslValue for Value { - type Str = Str; - type Exp = Exp; - fn value (&self) -> &Value { - self - } -} - -pub enum Value { - Nil, - Err(DslError), - Num(usize), - Sym(S), - Key(S), - Str(S), - Exp(usize, X), -} - -impl Default for Value { - fn default () -> Self { Self:: Nil } -} - -impl PartialEq for Value { - fn eq (&self, other: &Self) -> bool { - use Value::*; - match (self, other) { - (Nil, Nil) => true, - (Err(e1), Err(e2)) if e1 == e2 => true, - (Num(n1), Num(n2)) if n1 == n2 => true, - (Sym(s1), Sym(s2)) if s1 == s2 => true, - (Key(s1), Key(s2)) if s1 == s2 => true, - (Str(s1), Str(s2)) if s1 == s2 => true, - (Exp(d1, e1), Exp(d2, e2)) if d1 == d2 && e1 == e2 => true, - _ => false - } - } -} - -impl Clone for Value { - fn clone (&self) -> Self { - use Value::*; - match self { - Nil => Nil, - Err(e) => Err(e.clone()), - Num(n) => Num(*n), - Sym(s) => Sym(s.clone()), - Key(s) => Key(s.clone()), - Str(s) => Str(s.clone()), - Exp(d, e) => Exp(*d, e.clone()), - } - } -} - -impl Copy for Value {} - -impl Debug for Value { - fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - todo!() - } -} - -impl Display for Value { - fn fmt (&self, _f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - todo!() - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 485f09e..45fb178 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -35,17 +35,118 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] pub(crate) use ::tengri_core::*; -pub(crate) use std::fmt::Debug; -pub(crate) use std::fmt::{Display, Formatter, Error as FormatError}; +pub(crate) use std::fmt::Debug;//, Display};//, Formatter, Error as FormatError}; +pub(crate) use std::sync::Arc; +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::*; -mod dsl_ast; pub use self::dsl_ast::*; -mod dsl_cst; pub use self::dsl_cst::*; -mod dsl_display; //pub use self::dsl_display::*; -mod dsl_error; pub use self::dsl_error::*; -mod dsl_iter; pub use self::dsl_iter::*; -mod dsl_token; pub use self::dsl_token::*; -mod dsl_value; pub use self::dsl_value::*; -#[cfg(test)] mod dsl_test; + +mod dsl; pub use self::dsl::*; +mod ast; pub use self::ast::*; +mod cst; pub use self::cst::*; + +#[cfg(test)] mod test_token_iter { + use crate::*; + //use proptest::prelude::*; + #[test] fn test_iters () { + let mut iter = crate::SourceIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] const fn test_const_iters () { + let iter = crate::SourceConstIter::new(&":foo :bar"); + let _ = iter.next(); + } + #[test] fn test_num () { + let _digit = to_digit('0'); + let _digit = to_digit('x'); + let _number = to_number(&"123"); + let _number = to_number(&"12asdf3"); + } + //proptest! { + //#[test] fn proptest_source_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::SourceIter::new(&source); + ////let _ = iter.next() + //} + //#[test] fn proptest_token_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::TokenIter::new(&source); + ////let _ = iter.next(); + //} + //} +} + +//#[cfg(test)] mod test_token_prop { + //use crate::{Cst, CstMeta, Value::*}; + //use proptest::prelude::*; + //proptest! { + //#[test] fn test_token_prop ( + //source in "\\PC*", + //start in usize::MIN..usize::MAX, + //length in usize::MIN..usize::MAX, + //) { + //let token = Cst(Nil, CstMeta { source: &source, start, length }); + //let _ = token.slice(); + //} + //} +//} + +#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { + use crate::DslValue::*; + let source = ":f00"; + let mut token = Cst::new(source, 0, 1, Sym(":")); + token = token.grow_sym(); + assert_eq!(token, Cst::new(source, 0, 2, Sym(":f"))); + token = token.grow_sym(); + assert_eq!(token, Cst::new(source, 0, 3, Sym(":f0"))); + token = token.grow_sym(); + assert_eq!(token, Cst::new(source, 0, 4, Sym(":f00"))); + + assert_eq!(None, SourceIter::new("").next()); + assert_eq!(None, SourceIter::new(" \n \r \t ").next()); + + assert_eq!(Err(Unexpected('a')), SourceIter::new(" 9a ").next().unwrap().value()); + + assert_eq!(Num(7), SourceIter::new("7").next().unwrap().value()); + assert_eq!(Num(100), SourceIter::new(" 100 ").next().unwrap().value()); + + assert_eq!(Sym(":123foo"), SourceIter::new(" :123foo ").next().unwrap().value()); + assert_eq!(Sym("@bar456"), SourceIter::new(" \r\r\n\n@bar456\t\t\t").next().unwrap().value()); + + assert_eq!(Key("foo123"), SourceIter::new("foo123").next().unwrap().value()); + assert_eq!(Key("foo/bar"), SourceIter::new("foo/bar").next().unwrap().value()); + + assert_eq!(Str("foo/bar"), SourceIter::new("\"foo/bar\"").next().unwrap().value()); + assert_eq!(Str("foo/bar"), SourceIter::new(" \"foo/bar\" ").next().unwrap().value()); + + Ok(()) +} + +//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { + //// 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. + //let mut view = TokenIter(source); + //// The token iterator wraps a const token+source iterator. + //assert_eq!(view.0.0, source); + //let mut expr = view.peek(); + //assert_eq!(view.0.0, source); + //assert_eq!(expr, Some(Token { + //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter::new(&source[1..])) + //})); + ////panic!("{view:?}"); + ////panic!("{:#?}", expr); + ////for example in [ + ////include_str!("../../tui/examples/edn01.edn"), + ////include_str!("../../tui/examples/edn02.edn"), + ////] { + //////let items = Dsl::read_all(example)?; + //////panic!("{layout:?}"); + //////let content = >::from(&layout); + ////} + //Ok(()) +//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 76f3c3b..dba8bd3 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,51 +1,55 @@ use crate::*; -//use std::marker::PhantomData; -use std::fmt::Debug; +/// A collection of input bindings. +/// +/// 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(Default, Debug)] pub struct InputLayer { - condition: Option, - bindings: Ast, -} +/// A single input binding layer. +#[derive(Default, Debug)] struct InputLayer { condition: Option, bindings: Ast, } 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 { - Self(vec![]).layer(layer) + Self::default().layer(layer) } + /// Add layer, return `Self`. pub fn layer (mut self, layer: Ast) -> Self { self.add_layer(layer); self } + /// Add conditional layer, return `Self`. pub fn layer_if (mut self, condition: Ast, layer: Ast) -> Self { self.add_layer_if(Some(condition), layer); self } + /// Add layer, return `&mut Self`. pub fn add_layer (&mut self, layer: Ast) -> &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 { self.0.push(InputLayer { condition, bindings }); self } - pub fn handle + Eval, I: Eval, O: Command> (&self, state: &mut S, input: I) -> Perhaps { - InputHandle(state, self.0.as_slice()).try_eval(input) - } -} - -pub struct InputHandle<'a, S>(&'a mut S, &'a [InputLayer]); - -impl<'a, S: Eval + Eval, I: Eval, O: Command> Eval for InputHandle<'a, S> { - fn try_eval (&self, input: I) -> Perhaps { - let Self(state, layers) = 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, + 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")?; } if matches - && let Some(exp) = bindings.exp() - && let Some(ast) = exp.peek() - && input.eval(ast.clone(), ||"InputLayers: input.eval(binding) failed")? - && let Some(command) = state.try_eval(ast)? { + && let Some(exp) = bindings.0.exp_head() + && input.eval(Ast(exp.clone()), ||"InputLayers: input.eval(binding) failed")? + && let Some(command) = state.try_eval(exp)? { return Ok(Some(command)) } } diff --git a/output/src/lib.rs b/output/src/lib.rs index 5407df5..e8fdb72 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,17 +3,178 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use tengri_core::*; pub(crate) use std::marker::PhantomData; - -#[cfg(feature = "dsl")] -pub(crate) use ::tengri_dsl::*; - -mod space; pub use self::space::*; - -mod ops; pub use self::ops::*; - -#[cfg(feature = "dsl")] mod ops_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 + //}) +} diff --git a/output/src/ops/transform.rs b/output/src/ops/transform.rs index e69de29..415c416 100644 --- a/output/src/ops/transform.rs +++ b/output/src/ops/transform.rs @@ -0,0 +1,192 @@ +//! [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/output/src/ops_dsl.rs b/output/src/ops_dsl.rs deleted file mode 100644 index 94ded3f..0000000 --- a/output/src/ops_dsl.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::*; -use Value::*; - -impl Dsl for When where S: Eval + Eval { - fn try_provide (state: &S, source: impl DslValue) -> Perhaps { - if let Exp(_, mut exp) = source.value() - && let Some(Ast(Key(id))) = exp.peek() && *id == *"when" { - let _ = exp.next(); - return Ok(Some(Self( - state.eval(exp.next().unwrap(), ||"when: expected condition")?, - state.eval(exp.next().unwrap(), ||"when: expected content")?, - ))) - } - Ok(None) - } -} - -impl Dsl for Either where S: Eval + Eval + Eval { - fn try_provide (state: &S, source: impl DslValue) -> Perhaps { - 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: expected condition")?, - state.eval(exp.next().unwrap(), ||"either: expected content 1")?, - state.eval(exp.next().unwrap(), ||"either: expected content 2")?, - ))) - } - Ok(None) - } -} - -impl Dsl for Align where S: Eval, A> { - fn try_provide (state: &S, source: impl DslValue) -> Perhaps { - 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: expected content")?), - Some("align/x") => Self::x(state.eval(rest.next(), ||"align/x: expected content")?), - Some("align/y") => Self::y(state.eval(rest.next(), ||"align/y: expected content")?), - Some("align/n") => Self::n(state.eval(rest.next(), ||"align/n: expected content")?), - Some("align/s") => Self::s(state.eval(rest.next(), ||"align/s: expected content")?), - Some("align/e") => Self::e(state.eval(rest.next(), ||"align/e: expected content")?), - Some("align/w") => Self::w(state.eval(rest.next(), ||"align/w: expected content")?), - Some("align/nw") => Self::nw(state.eval(rest.next(), ||"align/nw: expected content")?), - Some("align/ne") => Self::ne(state.eval(rest.next(), ||"align/ne: expected content")?), - Some("align/sw") => Self::sw(state.eval(rest.next(), ||"align/sw: expected content")?), - Some("align/se") => Self::se(state.eval(rest.next(), ||"align/se: expected content")?), - _ => return Ok(None), - })) - } - Ok(None) - } -} - -impl Dsl for Bsp where S: Eval, A> + Eval, B> { - fn try_provide (state: &S, source: impl DslValue) -> Perhaps { - 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: expected content 1")?, - state.eval(rest.next(), ||"bsp/n: expected content 2")?, - ), - Some("bsp/s") => Self::s( - state.eval(rest.next(), ||"bsp/s: expected content 1")?, - state.eval(rest.next(), ||"bsp/s: expected content 2")?, - ), - Some("bsp/e") => Self::e( - state.eval(rest.next(), ||"bsp/e: expected content 1")?, - state.eval(rest.next(), ||"bsp/e: expected content 2")?, - ), - Some("bsp/w") => Self::w( - state.eval(rest.next(), ||"bsp/w: expected content 1")?, - state.eval(rest.next(), ||"bsp/w: expected content 2")?, - ), - Some("bsp/a") => Self::a( - state.eval(rest.next(), ||"bsp/a: expected content 1")?, - state.eval(rest.next(), ||"bsp/a: expected content 2")?, - ), - Some("bsp/b") => Self::b( - state.eval(rest.next(), ||"bsp/b: expected content 1")?, - state.eval(rest.next(), ||"bsp/b: expected content 2")?, - ), - _ => return Ok(None), - })) - } - 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 - //})); From 11f686650fc2b053a10e97c9f8c6e5c4316f606e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 21 Jun 2025 13:48:45 +0300 Subject: [PATCH 112/178] wip: fix(dsl): maybe getting somewhere? --- core/src/lib.rs | 4 +- dsl/src/ast.rs | 51 ++- dsl/src/cst.rs | 447 +++++++++++---------- dsl/src/dsl.rs | 178 +++++--- dsl/src/lib.rs | 141 +------ dsl/src/test.rs | 104 +++++ input/src/input_dsl.rs | 56 ++- input/src/lib.rs | 2 +- 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 | 24 +- tengri/src/test.rs | 83 ++-- tui/examples/tui.rs | 72 ++-- vin/Cargo.toml | 0 19 files changed, 964 insertions(+), 918 deletions(-) create mode 100644 dsl/src/test.rs 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 create mode 100644 vin/Cargo.toml 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..ac85d87 100644 --- a/dsl/src/ast.rs +++ b/dsl/src/ast.rs @@ -1,31 +1,38 @@ +//! The abstract syntax tree (AST) can be produced from the CST +//! by cloning source slices into owned ([Arc]) string slices. use crate::*; #[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub AstValue); +pub struct Ast(pub Arc, Ast>>>); -/// 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<'src> From> for Ast { - fn from (token: Cst<'src>) -> Self { - token.value().into() +impl Dsl for Ast { + type Str = Arc; + type Exp = Ast; + fn nth (&self, index: usize) -> Option> { + self.0.get(index).cloned() } } -impl<'src> From> for Ast { - fn from (value: CstValue<'src>) -> Self { - 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()), - }) +impl<'s> From> for Ast { + fn from (cst: Cst<'s>) -> Self { + Self(VecDeque::from([dsl_val(cst.val())]).into()) + } +} + +impl<'s> From> for Ast { + fn from (cst: CstIter<'s>) -> Self { + Self(cst.map(|x|x.value.into()).collect::>().into()) + } +} + +impl<'s> From> for Ast { + fn from (cst: CstVal<'s>) -> Self { + Self(VecDeque::from([dsl_val(cst.val())]).into()) + } +} + +impl<'s> From>> for DslVal, Ast> { + fn from (cst: DslVal<&'s str, CstIter<'s>>) -> Self { + dsl_val(cst) } } diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs index 3dff71f..a993760 100644 --- a/dsl/src/cst.rs +++ b/dsl/src/cst.rs @@ -1,9 +1,219 @@ //! The concrete syntax tree (CST) implements zero-copy //! parsing of the DSL from a string reference. CST items //! preserve info about their location in the source. - use crate::*; +/// CST stores strings as source references and expressions as [CstIter] instances. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Cst<'src>(pub CstIter<'src>); +impl<'src> Dsl for Cst<'src> { + type Str = &'src str; + type Exp = CstIter<'src>; + fn nth (&self, index: usize) -> Option> { + self.0.nth(index) + } +} + +/// Parsed substring with range and value. +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct CstVal<'src> { + /// Meaning of token. + pub value: DslVal<&'src str, CstIter<'src>>, + /// Reference to source text. + pub source: &'src str, + /// Index of 1st character of token. + pub start: usize, + /// Length of token. + pub length: usize, +} +impl<'src> Dsl for CstVal<'src> { + type Str = &'src str; + type Exp = CstIter<'src>; + fn nth (&self, index: usize) -> Option> { + todo!() + } +} + +impl<'src> CstVal<'src> { + pub const fn new ( + source: &'src str, + start: usize, + length: usize, + value: DslVal<&'src str, CstIter<'src>> + ) -> Self { + Self { source, start, length, value } + } + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) + } + pub const fn slice (&'src self) -> &'src str { + self.slice_source(self.source) + } + pub const fn slice_source <'range> (&'src self, source: &'range str) -> &'range str { + str_range(source, self.start, self.end()) + } + pub const fn slice_source_exp <'range> (&'src self, source: &'range str) -> &'range str { + str_range(source, self.start.saturating_add(1), self.end()) + } + pub const fn with_value (self, value: DslVal<&'src str, CstIter<'src>>) -> Self { + Self { value, ..self } + } + pub const fn value (&self) -> DslVal<&'src str, CstIter<'src>> { + self.value + } + 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: 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(DslVal::Key(token.slice_source(self.source))) + } + pub const fn grow_sym (self) -> Self { + let token = self.grow(); + token.with_value(DslVal::Sym(token.slice_source(self.source))) + } + pub const fn grow_str (self) -> Self { + let token = self.grow(); + token.with_value(DslVal::Str(token.slice_source(self.source))) + } + pub const fn grow_exp (self) -> Self { + let token = self.grow(); + if let DslVal::Exp(depth, _) = token.value() { + token.with_value(DslVal::Exp(depth, CstIter::new(token.slice_source_exp(self.source)))) + } else { + unreachable!() + } + } + pub const fn grow_in (self) -> Self { + let token = self.grow_exp(); + 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 DslVal::Exp(depth, source) = token.value() { + if depth > 0 { + token.with_value(DslVal::Exp(depth - 1, source)) + } else { + return self.error(Unexpected(')')) + } + } else { + unreachable!() + } + } +} + +/// Provides a native [Iterator] API over [CstConstIter], +/// emitting [Cst] items. +/// +/// [Cst::next] returns just the [Cst] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstIter<'src>(pub CstConstIter<'src>); +impl<'src> Dsl for CstIter<'src> { + type Str = &'src str; + type Exp = Self; + fn nth (&self, index: usize) -> Option> { + use DslVal::*; + self.0.nth(index).map(|x|match x { + Nil => Nil, + Err(e) => Err(e), + Num(u) => Num(u), + Sym(s) => Sym(s), + Key(s) => Sym(s), + Str(s) => Sym(s), + Exp(d, x) => DslVal::Exp(d, CstIter(x)), + }) + } +} +impl<'src> CstIter<'src> { + pub const fn new (source: &'src str) -> Self { + Self(CstConstIter::new(source)) + } + pub const fn peek (&self) -> Option> { + self.0.peek() + } +} +impl<'src> Iterator for CstIter<'src> { + type Item = CstVal<'src>; + fn next (&mut self) -> Option> { + self.0.next().map(|(item, rest)|{ + self.0 = rest; + item + }) + } +} +impl<'src> Into>> for CstIter<'src> { + fn into (self) -> Vec> { + self.collect() + } +} +impl<'src> Into> for CstIter<'src> { + fn into (self) -> Vec { + self.map(Into::into).collect() + } +} + +/// Owns a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [Cst] and +/// * the source text remaining +/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstConstIter<'src>(pub &'src str); +impl<'src> Dsl for CstConstIter<'src> { + type Str = &'src str; + type Exp = Self; + fn nth (&self, mut index: usize) -> Option> { + use DslVal::*; + let mut iter = self.clone(); + for i in 0..index { + iter = iter.next()?.1 + } + iter.next().map(|(x, _)|match x.value { + Nil => Nil, + Err(e) => Err(e), + Num(u) => Num(u), + Sym(s) => Sym(s), + Key(s) => Sym(s), + Str(s) => Sym(s), + Exp(d, x) => DslVal::Exp(d, x.0), + }) + } +} +impl<'src> CstConstIter<'src> { + pub const fn new (source: &'src str) -> Self { + Self(source) + } + pub const fn chomp (&self, index: usize) -> Self { + Self(split_at(self.0, index).1) + } + pub const fn next (mut self) -> Option<(CstVal<'src>, Self)> { + Self::next_mut(&mut self) + } + pub const fn peek (&self) -> Option> { + peek_src(self.0) + } + pub const fn next_mut (&mut self) -> Option<(CstVal<'src>, Self)> { + match self.peek() { + Some(token) => Some((token, self.chomp(token.end()))), + None => None + } + } +} + /// Implement the const iterator pattern. macro_rules! const_iter { ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { @@ -19,6 +229,10 @@ macro_rules! const_iter { } } +const_iter!(<'src>|self: CstConstIter<'src>| + => CstVal<'src> + => self.next_mut().map(|(result, _)|result)); + /// Static iteration helper used by [cst]. macro_rules! iterate { ($expr:expr => $arg: pat => $body:expr) => { @@ -30,140 +244,26 @@ macro_rules! iterate { } } -/// CST stores strings as source references and expressions as [SourceIter] instances. -pub type CstValue<'source> = DslValue<&'source str, SourceIter<'source>>; - -/// Token sharing memory with source reference. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Cst<'src> { - /// Reference to source text. - pub source: &'src str, - /// Index of 1st character of token. - pub start: usize, - /// Length of token. - pub length: usize, - /// Meaning of token. - pub value: CstValue<'src>, -} - -impl<'src> Cst<'src> { - pub const fn new ( - source: &'src str, - start: usize, - length: usize, - value: CstValue<'src> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'src self) -> &'src str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'src self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'src self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: CstValue<'src>) -> Self { - Self { value, ..self } - } - 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 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() }, - } - } - pub const fn grow_key (self) -> Self { - let token = self.grow(); - token.with_value(DslValue::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))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(DslValue::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)))) - } 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)) - } else { - unreachable!() - } - } - pub const fn grow_out (self) -> Self { - let token = self.grow_exp(); - if let DslValue::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(DslValue::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} - -pub const fn peek_src <'src> (source: &'src str) -> Option> { - use DslValue::*; - let mut token: Cst<'src> = Cst::new(source, 0, 0, Nil); +pub const fn peek_src <'src> (source: &'src str) -> Option> { + use DslVal::*; + let mut token: CstVal<'src> = CstVal::new(source, 0, 0, Nil); iterate!(char_indices(source) => (start, c) => token = match token.value() { Err(_) => return Some(token), Nil => match c { ' '|'\n'|'\r'|'\t' => token.grow(), '(' => - Cst::new(source, start, 1, Exp(1, SourceIter::new(str_range(source, start, start + 1)))), + CstVal::new(source, start, 1, Exp(1, CstIter::new(str_range(source, start, start + 1)))), '"' => - Cst::new(source, start, 1, Str(str_range(source, start, start + 1))), + CstVal::new(source, start, 1, Str(str_range(source, start, start + 1))), ':'|'@' => - Cst::new(source, start, 1, Sym(str_range(source, start, start + 1))), + CstVal::new(source, start, 1, Sym(str_range(source, start, start + 1))), '/'|'a'..='z' => - Cst::new(source, start, 1, Key(str_range(source, start, start + 1))), + CstVal::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) + CstVal::new(source, start, 1, match to_digit(c) { + Ok(c) => DslVal::Num(c), + Result::Err(e) => DslVal::Err(e) }), _ => token.error(Unexpected(c)) }, @@ -201,90 +301,19 @@ pub const fn peek_src <'src> (source: &'src str) -> Option> { } } -/// Owns a reference to the source text. -/// [SourceConstIter::next] emits subsequent pairs of: -/// * a [Cst] and -/// * the source text remaining -/// * [ ] TODO: maybe [SourceConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceConstIter<'src>(pub &'src str); - -impl<'src> From> for SourceIter<'src> { - fn from (source: SourceConstIter<'src>) -> Self{ - Self(source) - } +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) + }); + Ok(value) } -impl<'src> From<&'src str> for SourceConstIter<'src> { - fn from (source: &'src str) -> Self{ - Self::new(source) - } -} - -impl<'src> SourceConstIter<'src> { - pub const fn new (source: &'src str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(Cst<'src>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(Cst<'src>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} - -const_iter!(<'src>|self: SourceConstIter<'src>| => Cst<'src> => self.next_mut().map(|(result, _)|result)); - -/// Provides a native [Iterator] API over [SourceConstIter], -/// emitting [Cst] items. -/// -/// [Cst::next] returns just the [Cst] and mutates `self`, -/// instead of returning an updated version of the struct as [SourceConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct SourceIter<'src>(pub SourceConstIter<'src>); - -impl<'src> SourceIter<'src> { - pub const fn new (source: &'src str) -> Self { - Self(SourceConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} - -impl<'src> Iterator for SourceIter<'src> { - type Item = Cst<'src>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{ - self.0 = rest; - item - }) - } -} - -impl<'src> From<&'src str> for SourceIter<'src> { - fn from (source: &'src str) -> Self{ - Self(SourceConstIter(source)) - } -} - -impl<'src> Into>> for SourceIter<'src> { - fn into (self) -> Vec> { - self.collect() - } -} - -impl<'src> Into> for SourceIter<'src> { - fn into (self) -> Vec { - self.map(Into::into).collect() - } +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) + }) } diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 615804d..b2733e0 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -1,6 +1,11 @@ use crate::*; +use std::error::Error; -#[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 +18,23 @@ 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. +/// +/// * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] +/// * [DslVal::Exp] wraps an expression depth and a [CstIter] +/// with the remaining part of the expression. +/// * expression depth other that 0 mean unclosed parenthesis. +/// * closing and unopened parenthesis panics during reading. +/// * [ ] TODO: signed depth might be interesting +/// * [DslVal::Sym] and [DslVal::Key] are stringish literals +/// with slightly different parsing rules. +/// * [DslVal::Num] is an unsigned integer literal. #[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 +42,69 @@ pub enum DslValue { Exp(usize, Exp), } -impl DslValue { +pub trait Dsl { + type Str: PartialEq + Clone + Default + Debug + AsRef; + type Exp: PartialEq + Clone + Default + Debug + Dsl; + fn nth (&self, index: usize) -> Option>; + fn val (&self) -> DslVal { + self.nth(0).unwrap_or(DslVal::Nil) + } + // exp-only nth here? +} + +impl< + Str: PartialEq + Clone + Default + Debug + AsRef, + Exp: PartialEq + Clone + Default + Debug + Dsl, +> Dsl for DslVal { + type Str = Str; + type Exp = Exp; + fn val (&self) -> DslVal { + self.clone() + } + fn nth (&self, index: usize) -> Option> { + 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 +125,54 @@ impl DslValue { } } -impl Copy for DslValue {} +impl Copy for DslVal {} + +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) + } + } +} + +macro_rules! from_str { + ($Struct:ty |$source:ident| $expr:expr) => { + impl<'s> From<&'s str> for $Struct { + fn from ($source: &'s str) -> Self { + $expr + } + } + } +} + +from_str!(Ast|source|Self::from(CstIter::from(source))); +from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); +from_str!(CstIter<'s>|source|Self(CstConstIter(source))); +from_str!(CstConstIter<'s>|source|Self::new(source)); + +pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { + use DslVal::*; + match val { + 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.into()), + } +} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 45fb178..264fa19 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,152 +1,17 @@ -//! [Token]s are parsed substrings with an associated [Value]. -//! -//! * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -//! * [Value::Exp] wraps an expression depth and a [SourceIter] -//! with the remaining part of the expression. -//! * expression depth other that 0 mean unclosed parenthesis. -//! * closing and unopened parenthesis panics during reading. -//! * [ ] TODO: signed depth might be interesting -//! * [Value::Sym] and [Value::Key] are stringish literals -//! with slightly different parsing rules. -//! * [Value::Num] is an unsigned integer literal. -//!``` -//! use tengri_dsl::{*, Value::*}; -//! let source = include_str!("../test.edn"); -//! let mut view = TokenIter::new(source); -//! assert_eq!(view.peek(), Some(Token { -//! source, -//! start: 0, -//! length: source.len(), -//! value: Exp(0, TokenIter::new(&source[1..])) -//! })); -//!``` -//! The token iterator [TokenIter] allows you to get the -//! general-purpose syntactic [Token]s represented by the source text. -//! -//! Both iterators are `peek`able: -//! -//! ``` -//! let src = include_str!("../test.edn"); -//! let mut view = tengri_dsl::TokenIter::new(src); -//! assert_eq!(view.0.0, src); -//! assert_eq!(view.peek(), view.0.peek()) -//! ``` #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] pub(crate) use ::tengri_core::*; -pub(crate) use std::fmt::Debug;//, Display};//, Formatter, Error as FormatError}; +pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; 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::*; mod cst; pub use self::cst::*; -#[cfg(test)] mod test_token_iter { - use crate::*; - //use proptest::prelude::*; - #[test] fn test_iters () { - let mut iter = crate::SourceIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] const fn test_const_iters () { - let iter = crate::SourceConstIter::new(&":foo :bar"); - let _ = iter.next(); - } - #[test] fn test_num () { - let _digit = to_digit('0'); - let _digit = to_digit('x'); - let _number = to_number(&"123"); - let _number = to_number(&"12asdf3"); - } - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::SourceIter::new(&source); - ////let _ = iter.next() - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} -} - -//#[cfg(test)] mod test_token_prop { - //use crate::{Cst, CstMeta, Value::*}; - //use proptest::prelude::*; - //proptest! { - //#[test] fn test_token_prop ( - //source in "\\PC*", - //start in usize::MIN..usize::MAX, - //length in usize::MIN..usize::MAX, - //) { - //let token = Cst(Nil, CstMeta { source: &source, start, length }); - //let _ = token.slice(); - //} - //} -//} - -#[cfg(test)] #[test] fn test_token () -> Result<(), Box> { - use crate::DslValue::*; - let source = ":f00"; - let mut token = Cst::new(source, 0, 1, Sym(":")); - token = token.grow_sym(); - assert_eq!(token, Cst::new(source, 0, 2, Sym(":f"))); - token = token.grow_sym(); - assert_eq!(token, Cst::new(source, 0, 3, Sym(":f0"))); - token = token.grow_sym(); - assert_eq!(token, Cst::new(source, 0, 4, Sym(":f00"))); - - assert_eq!(None, SourceIter::new("").next()); - assert_eq!(None, SourceIter::new(" \n \r \t ").next()); - - assert_eq!(Err(Unexpected('a')), SourceIter::new(" 9a ").next().unwrap().value()); - - assert_eq!(Num(7), SourceIter::new("7").next().unwrap().value()); - assert_eq!(Num(100), SourceIter::new(" 100 ").next().unwrap().value()); - - assert_eq!(Sym(":123foo"), SourceIter::new(" :123foo ").next().unwrap().value()); - assert_eq!(Sym("@bar456"), SourceIter::new(" \r\r\n\n@bar456\t\t\t").next().unwrap().value()); - - assert_eq!(Key("foo123"), SourceIter::new("foo123").next().unwrap().value()); - assert_eq!(Key("foo/bar"), SourceIter::new("foo/bar").next().unwrap().value()); - - assert_eq!(Str("foo/bar"), SourceIter::new("\"foo/bar\"").next().unwrap().value()); - assert_eq!(Str("foo/bar"), SourceIter::new(" \"foo/bar\" ").next().unwrap().value()); - - Ok(()) -} - -//#[cfg(test)] #[test] fn test_examples () -> Result<(), DslError> { - //// 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. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, SourceIter::new(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} +#[cfg(test)] mod test; diff --git a/dsl/src/test.rs b/dsl/src/test.rs new file mode 100644 index 0000000..b275573 --- /dev/null +++ b/dsl/src/test.rs @@ -0,0 +1,104 @@ +use crate::*; +use proptest::prelude::*; + +#[test] fn test_iters () { + let mut iter = crate::CstIter::new(&":foo :bar"); + let _ = iter.next(); +} + +#[test] const fn test_const_iters () { + let iter = crate::CstConstIter::new(&":foo :bar"); + let _ = iter.next(); +} + +#[test] fn test_num () { + let _digit = to_digit('0'); + let _digit = to_digit('x'); + let _number = to_number(&"123"); + let _number = to_number(&"12asdf3"); +} + //proptest! { + //#[test] fn proptest_source_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::CstIter::new(&source); + ////let _ = iter.next() + //} + //#[test] fn proptest_token_iter ( + //source in "\\PC*" + //) { + //let mut iter = crate::TokenIter::new(&source); + ////let _ = iter.next(); + //} + //} + +//#[cfg(test)] mod test_token_prop { + //use crate::{Cst, CstMeta, Value::*}; + //use proptest::prelude::*; + //proptest! { + //#[test] fn test_token_prop ( + //source in "\\PC*", + //start in usize::MIN..usize::MAX, + //length in usize::MIN..usize::MAX, + //) { + //let token = Cst(Nil, CstMeta { source: &source, start, length }); + //let _ = token.slice(); + //} + //} +//} + +#[test] fn test_token () -> Result<(), Box> { + use crate::DslVal::*; + let source = ":f00"; + let mut token = CstVal::new(source, 0, 1, Sym(":")); + token = token.grow_sym(); + assert_eq!(token, CstVal::new(source, 0, 2, Sym(":f"))); + token = token.grow_sym(); + assert_eq!(token, CstVal::new(source, 0, 3, Sym(":f0"))); + token = token.grow_sym(); + assert_eq!(token, CstVal::new(source, 0, 4, Sym(":f00"))); + + assert_eq!(None, CstIter::new("").next()); + assert_eq!(None, CstIter::new(" \n \r \t ").next()); + + assert_eq!(Err(Unexpected('a')), CstIter::new(" 9a ").next().unwrap().value()); + + assert_eq!(Num(7), CstIter::new("7").next().unwrap().value()); + assert_eq!(Num(100), CstIter::new(" 100 ").next().unwrap().value()); + + assert_eq!(Sym(":123foo"), CstIter::new(" :123foo ").next().unwrap().value()); + assert_eq!(Sym("@bar456"), CstIter::new(" \r\r\n\n@bar456\t\t\t").next().unwrap().value()); + + assert_eq!(Key("foo123"), CstIter::new("foo123").next().unwrap().value()); + assert_eq!(Key("foo/bar"), CstIter::new("foo/bar").next().unwrap().value()); + + //assert_eq!(Str("foo/bar"), CstIter::new("\"foo/bar\"").next().unwrap().value()); + //assert_eq!(Str("foo/bar"), CstIter::new(" \"foo/bar\" ").next().unwrap().value()); + + Ok(()) +} + +//#[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. + //let mut view = TokenIter(source); + //// The token iterator wraps a const token+source iterator. + //assert_eq!(view.0.0, source); + //let mut expr = view.peek(); + //assert_eq!(view.0.0, source); + //assert_eq!(expr, Some(Token { + //source, start: 0, length: source.len() - 1, value: Exp(0, CstIter::new(&source[1..])) + //})); + ////panic!("{view:?}"); + ////panic!("{:#?}", expr); + ////for example in [ + ////include_str!("../../tui/examples/edn01.edn"), + ////include_str!("../../tui/examples/edn02.edn"), + ////] { + //////let items = Dsl::read_all(example)?; + //////panic!("{layout:?}"); + //////let content = >::from(&layout); + ////} + //Ok(()) +//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index dba8bd3..d28dc79 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -5,51 +5,77 @@ 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>); /// A single input binding layer. -#[derive(Default, Debug)] struct InputLayer { condition: Option, bindings: Ast, } +#[derive(Default, Debug)] struct InputLayer { + condition: Option>, + bindings: DslVal, +} +/// Input layers start out empty regardless if `T` implements [Default]. +impl Default for InputLayers { fn default () -> Self { Self(vec![]) } } +/// Bring up an input layer map from a string representation. +impl<'s> From<&'s str> for InputLayers { + fn from (source: &'s str) -> Self { + let mut layers = vec![]; + let mut source = CstIter::from(source); + while let Some(cst) = source.next() { + panic!("{:?}", cst.value); + layers.push(match cst.value.exp_head().map(|x|x.as_key()).flatten() { + Some("layer") => InputLayer { + condition: None, + bindings: dsl_val(source.nth(1).unwrap()), + }, + Some("layer-if") => InputLayer { + condition: Some(dsl_val(source.nth(1).unwrap())), + bindings: dsl_val(source.nth(2).unwrap()), + }, + _ => panic!("this shoulda been a TryFrom"), + }); + } + Self(layers) + } +} -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: DslVal) -> Self { Self::default().layer(layer) } /// Add layer, return `Self`. - pub fn layer (mut self, layer: Ast) -> Self { + pub fn layer (mut self, layer: DslVal) -> 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: DslVal, layer: DslVal) -> 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: DslVal) -> &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: DslVal) -> &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/input/src/lib.rs b/input/src/lib.rs index 1324e15..757ab88 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -33,6 +33,6 @@ mod input_handle; pub use self::input_handle::*; } #[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { - let _keymap = SourceIter::new(""); + let _keymap = CstIter::new(""); Ok(()) } 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..12012c3 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -44,15 +44,16 @@ 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, ||Box::new("failed to load builtin".into()))? + .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 +64,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..5b3f3d5 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,18 +1,16 @@ use crate::*; +use crate::{dsl::*, input::*, tui::TuiIn}; +use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; #[test] fn test_subcommand () -> Usually<()> { - use crate::tui::TuiIn; - use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; - use crate::input::*; - use crate::dsl::*; - struct Test { - keys: InputLayers - } - handle!(TuiIn: |self: Test, input|if let Some(command) = self.keys.command(self, input) { + struct Test { keys: InputLayers } + + handle!(TuiIn: |self: Test, input|Ok(None));/*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 +23,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,44 +33,46 @@ use crate::*; Ok(None) } } + let mut test = Test { - keys: InputLayers::new(SourceIter::new(" + keys: InputLayers::from(" (@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'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('b'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('c'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('d'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); - assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - kind: KeyEventKind::Press, - code: KeyCode::Char('z'), - modifiers: KeyModifiers::NONE, - state: KeyEventState::NONE, - })))?); + + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('a'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('b'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('c'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('d'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('z'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); Ok(()) } diff --git a/tui/examples/tui.rs b/tui/examples/tui.rs index 63312e8..1d0124f 100644 --- a/tui/examples/tui.rs +++ b/tui/examples/tui.rs @@ -33,23 +33,23 @@ const EXAMPLES: &'static [&'static str] = &[ ]; handle!(TuiIn: |self: Example, input|{ - Ok(if let Some(command) = SourceIter::new(KEYMAP).command::<_, ExampleCommand, _>(self, input) { + Ok(None)/*if let Some(command) = CstIter::new(KEYMAP).command::<_, ExampleCommand, _>(self, input) { command.execute(self)?; Some(true) } else { None - }) + })*/ }); -#[tengri_proc::expose] -impl Example { - fn _todo_u16_stub (&self) -> u16 { todo!() } - fn _todo_bool_stub (&self) -> bool { todo!() } - fn _todo_usize_stub (&self) -> usize { todo!() } - //[bool] => {} - //[u16] => {} - //[usize] => {} -} +//#[tengri_proc::expose] +//impl Example { + //fn _todo_u16_stub (&self) -> u16 { todo!() } + //fn _todo_bool_stub (&self) -> bool { todo!() } + //fn _todo_usize_stub (&self) -> usize { todo!() } + ////[bool] => {} + ////[u16] => {} + ////[usize] => {} +//} #[tengri_proc::command(Example)] impl ExampleCommand { @@ -70,31 +70,31 @@ 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, CstIter::new(src))); self.1.of(Bsp::s(title, Bsp::n(""/*code*/, content))) }); -#[tengri_proc::view(TuiOut)] -impl Example { - pub fn title (&self) -> impl Content + use<'_> { - Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed() - } - pub fn code (&self) -> impl Content + use<'_> { - Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed() - } - pub fn hello (&self) -> impl Content + use<'_> { - Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() - } - pub fn world (&self) -> impl Content + use<'_> { - Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() - } - pub fn hello_world (&self) -> impl Content + use<'_> { - "Hello world!".boxed() - } - pub fn map_e (&self) -> impl Content + use<'_> { - Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - } - pub fn map_s (&self) -> impl Content + use<'_> { - Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() - } -} +//#[tengri_proc::view(TuiOut)] +//impl Example { + //pub fn title (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(60, 10, 10), Push::y(1, Align::n(format!("Example {}/{}:", self.0 + 1, EXAMPLES.len())))).boxed() + //} + //pub fn code (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(10, 60, 10), Push::y(2, Align::n(format!("{}", EXAMPLES[self.0])))).boxed() + //} + //pub fn hello (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(10, 100, 10), "Hello").boxed() + //} + //pub fn world (&self) -> impl Content + use<'_> { + //Tui::bg(Color::Rgb(100, 10, 10), "world").boxed() + //} + //pub fn hello_world (&self) -> impl Content + use<'_> { + //"Hello world!".boxed() + //} + //pub fn map_e (&self) -> impl Content + use<'_> { + //Map::east(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} + //pub fn map_s (&self) -> impl Content + use<'_> { + //Map::south(5u16, ||0..5u16, |n, _i|format!("{n}")).boxed() + //} +//} diff --git a/vin/Cargo.toml b/vin/Cargo.toml new file mode 100644 index 0000000..e69de29 From 6c3a0964ecda0abcf372b8fac2fd0f263642c69c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 22 Jun 2025 09:40:28 +0300 Subject: [PATCH 113/178] wip: fix(input): grr todo: per-key inptut map layering --- ] | 200 ++++++++++++++++++++++++++++++++++ dsl/src/lib.rs | 2 + input/src/input_dsl.rs | 241 ++++++++++++++++++++++++++++++----------- input/src/lib.rs | 4 + tengri/src/test.rs | 6 +- 5 files changed, 388 insertions(+), 65 deletions(-) create mode 100644 ] diff --git a/] b/] new file mode 100644 index 0000000..93bdbbd --- /dev/null +++ b/] @@ -0,0 +1,200 @@ +use crate::*; +/// A collection of input bind. +/// +/// Each contained layer defines a mapping from input event to command invocation +/// over a given state. Furthermore, each layer may have an associated cond, +/// so that only certain layers are active at a given time depending on state. +#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); +impl InputMap { + /// Create input layer collection from path to text file. + pub fn from_path > (path: P) -> Usually { + if !exists(path.as_ref())? { + return Err(format!("(e5) not found: {path:?}").into()) + } + Self::from_source(read_and_leak(path)?) + } + /// Create input layer collection from string. + pub fn from_source > (source: S) -> Usually { + Self::from_dsl(CstIter::from(source.as_ref())) + } + /// Create input layer collection from DSL. + pub fn from_dsl (dsl: impl Dsl) -> Usually { + use DslVal::*; + let mut input_map: Self = Self(Default::default()); + while let Exp(_, mut exp) = dsl.val() { + match exp.nth(0).map(|x|x.val()) { + Some(Str(path)) => { + let path = PathBuf::from(path.as_ref()); + let module = InputMap::::from_path(&path)?; + for (key, val) in module.0.into_iter() { + } + }, + Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { + //input_map.unconditional.push(expr); + todo!("binding"); + }, + Some(Exp(_, expr)) if expr.nth(0).map(|x|x.val()) == Some(Key("if")) => { + todo!("conditional binding"); + }, + _ => return Result::Err("invalid token in keymap".into()), + } + } + Ok(input_map) + } + /// 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: DslInto + DslInto, + O: Command + { + todo!(); + //let layers = self.0.as_slice(); + //for InputLayer { cond, bind } in layers.iter() { + //let mut matches = true; + //if let Some(cond) = cond { + //matches = state.dsl_into(cond, ||format!("input: no cond").into())?; + //} + //if matches + //&& let Some(exp) = bind.val().exp_head() + //&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? + //&& let Some(command) = state.try_dsl_into(exp)? { + //return Ok(Some(command)) + //} + //} + Ok(None) + } + /* + /// Create an input map with a single non-condal layer. + /// (Use [Default::default] to get an empty map.) + pub fn new (layer: DslVal) -> Self { + Self::default().layer(layer) + } + /// Add layer, return `Self`. + pub fn layer (mut self, layer: DslVal) -> Self { + self.add_layer(layer); self + } + /// Add condal layer, return `Self`. + pub fn layer_if (mut self, cond: DslVal, layer: DslVal) -> Self { + self.add_layer_if(Some(cond), layer); self + } + /// Add layer, return `&mut Self`. + pub fn add_layer (&mut self, layer: DslVal) -> &mut Self { + self.add_layer_if(None, layer.into()); self + } + /// Add condal layer, return `&mut Self`. + pub fn add_layer_if (&mut self, cond: Option>, bind: DslVal) -> &mut Self { + self.0.push(InputLayer { cond, bind }); + self + } + */ +} + + + + //let mut keys = iter.unwrap(); + //let mut map = InputMap::default(); + //while let Some(token) = keys.next() { + //if let Value::Exp(_, mut exp) = token.value { + //let next = exp.next(); + //if let Some(Token { value: Value::Key(sym), .. }) = next { + //match sym { + //"layer" => { + //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + //let path = base.as_ref().parent().unwrap().join(unquote(path)); + //if !std::fs::exists(&path)? { + //return Err(format!("(e5) not found: {path:?}").into()) + //} + //map.add_layer(read_and_leak(path)?.into()); + //print!("layer:\n path: {:?}...", exp.0.0.trim()); + //println!("ok"); + //} else { + //return Err(format!("(e4) unexpected non-string {next:?}").into()) + //} + //}, + + //"layer-if" => { + //let mut cond = None; + + //if let Some(Token { value: Value::Sym(sym), .. }) = exp.next() { + //cond = Some(leak(sym)); + //} else { + //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + //}; + + //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + //let path = base.as_ref().parent().unwrap().join(unquote(path)); + //if !std::fs::exists(&path)? { + //return Err(format!("(e5) not found: {path:?}").into()) + //} + //print!("layer-if:\n cond: {}\n path: {path:?}...", + //cond.unwrap_or_default()); + //let keys = read_and_leak(path)?.into(); + //let cond = cond.unwrap(); + //println!("ok"); + //map.add_layer_if( + //Box::new(move |state: &App|Take::take_or_fail( + //state, exp, ||"missing input layer conditional" + //)), keys + //); + //} else { + //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + //} + //}, + + //_ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) + //} + //} else { + //return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) + //} + //} else { + //return Err(format!("(e1) unexpected token {token:?}").into()) + //} + //} + //Ok(map) + //} +//{ +//} + //fn from (source: &'s str) -> Self { + //// this should be for single layer: + //use DslVal::*; + //let mut layers = vec![]; + //let mut source = CstIter::from(source); + //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { + //let mut iter = iter.clone(); + //layers.push(match iter.next().map(|x|x.value) { + //Some(Sym(sym)) if sym.starts_with("@") => InputLayer { + //cond: None, + //bind: vec![[ + //dsl_val(source.nth(1).unwrap()), + //dsl_val(source.nth(2).unwrap()), + //]], + //}, + //Some(Str(layer)) => InputLayer { + //cond: None, + //bind: dsl_val(source.nth(1).unwrap()), + //}, + //Some(Key("if")) => InputLayer { + //cond: Some(dsl_val(source.nth(1).unwrap())), + //bind: dsl_val(source.nth(2).unwrap()), + //}, + //_ => panic!("invalid token in keymap"), + //}) + //} + //Self(layers) + //} +//} + +fn unquote (x: &str) -> &str { + let mut chars = x.chars(); + chars.next(); + //chars.next_back(); + chars.as_str() +} + +fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { + Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) +} + +fn leak (x: impl AsRef) -> &'static str { + Box::leak(x.as_ref().into()) +} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 264fa19..aa255fc 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -5,6 +5,8 @@ pub(crate) use ::tengri_core::*; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; pub(crate) use std::collections::VecDeque; +pub(crate) use std::path::Path; +pub(crate) use std::fs::exists; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index d28dc79..e642490 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,43 +1,71 @@ use crate::*; - -/// A collection of input bindings. +/// A collection of input bind. /// /// Each contained layer defines a mapping from input event to command invocation -/// over a given state. Furthermore, each layer may have an associated condition, +/// over a given state. Furthermore, each layer may have an associated cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug)] pub struct InputLayers(Vec>); -/// A single input binding layer. -#[derive(Default, Debug)] struct InputLayer { - condition: Option>, - bindings: DslVal, -} -/// Input layers start out empty regardless if `T` implements [Default]. -impl Default for InputLayers { fn default () -> Self { Self(vec![]) } } -/// Bring up an input layer map from a string representation. -impl<'s> From<&'s str> for InputLayers { - fn from (source: &'s str) -> Self { - let mut layers = vec![]; - let mut source = CstIter::from(source); - while let Some(cst) = source.next() { - panic!("{:?}", cst.value); - layers.push(match cst.value.exp_head().map(|x|x.as_key()).flatten() { - Some("layer") => InputLayer { - condition: None, - bindings: dsl_val(source.nth(1).unwrap()), - }, - Some("layer-if") => InputLayer { - condition: Some(dsl_val(source.nth(1).unwrap())), - bindings: dsl_val(source.nth(2).unwrap()), - }, - _ => panic!("this shoulda been a TryFrom"), - }); +#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); +impl InputMap { + /// Create input layer collection from path to text file. + pub fn from_path > (path: P) -> Usually { + if !exists(path.as_ref())? { + return Err(format!("(e5) not found: {path:?}").into()) } - Self(layers) + Self::from_source(read_and_leak(path)?) } -} - -impl InputLayers { - /// Create an input map with a single non-conditional layer. + /// Create input layer collection from string. + pub fn from_source > (source: S) -> Usually { + Self::from_dsl(CstIter::from(source.as_ref())) + } + /// Create input layer collection from DSL. + pub fn from_dsl (dsl: impl Dsl) -> Usually { + use DslVal::*; + let mut input_map: Self = Self(Default::default()); + while let Exp(_, mut exp) = dsl.val() { + let val = exp.nth(0).map(|x|x.val()); + match val { + Some(Str(path)) => { + let path = PathBuf::from(path.as_ref()); + let module = InputMap::::from_path(&path)?; + for (key, val) in module.0.into_iter() { + } + }, + Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { + //input_map.unconditional.push(expr); + todo!("binding"); + }, + Some(Exp(_, expr)) if let Some(Key(key)) = expr.nth(0) && key.as_ref() == "if" => { + todo!("conditional binding"); + }, + _ => return Result::Err(format!("invalid token in keymap: {val:?}").into()), + } + } + Ok(input_map) + } + /// 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: DslInto + DslInto, + O: Command + { + todo!(); + //let layers = self.0.as_slice(); + //for InputLayer { cond, bind } in layers.iter() { + //let mut matches = true; + //if let Some(cond) = cond { + //matches = state.dsl_into(cond, ||format!("input: no cond").into())?; + //} + //if matches + //&& let Some(exp) = bind.val().exp_head() + //&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? + //&& let Some(command) = state.try_dsl_into(exp)? { + //return Ok(Some(command)) + //} + //} + Ok(None) + } + /* + /// Create an input map with a single non-condal layer. /// (Use [Default::default] to get an empty map.) pub fn new (layer: DslVal) -> Self { Self::default().layer(layer) @@ -46,39 +74,128 @@ impl InputLayers { pub fn layer (mut self, layer: DslVal) -> Self { self.add_layer(layer); self } - /// Add conditional layer, return `Self`. - pub fn layer_if (mut self, condition: DslVal, layer: DslVal) -> Self { - self.add_layer_if(Some(condition), layer); self + /// Add condal layer, return `Self`. + pub fn layer_if (mut self, cond: DslVal, layer: DslVal) -> Self { + self.add_layer_if(Some(cond), layer); self } /// Add layer, return `&mut Self`. pub fn add_layer (&mut self, layer: DslVal) -> &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: DslVal) -> &mut Self { - self.0.push(InputLayer { condition, bindings }); + /// Add condal layer, return `&mut Self`. + pub fn add_layer_if (&mut self, cond: Option>, bind: DslVal) -> &mut Self { + self.0.push(InputLayer { cond, bind }); 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: 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.dsl_into(condition, ||format!("input: no condition").into())?; - } - if matches - && 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)) - } - } - Ok(None) - } + */ +} + + + + //let mut keys = iter.unwrap(); + //let mut map = InputMap::default(); + //while let Some(token) = keys.next() { + //if let Value::Exp(_, mut exp) = token.value { + //let next = exp.next(); + //if let Some(Token { value: Value::Key(sym), .. }) = next { + //match sym { + //"layer" => { + //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + //let path = base.as_ref().parent().unwrap().join(unquote(path)); + //if !std::fs::exists(&path)? { + //return Err(format!("(e5) not found: {path:?}").into()) + //} + //map.add_layer(read_and_leak(path)?.into()); + //print!("layer:\n path: {:?}...", exp.0.0.trim()); + //println!("ok"); + //} else { + //return Err(format!("(e4) unexpected non-string {next:?}").into()) + //} + //}, + + //"layer-if" => { + //let mut cond = None; + + //if let Some(Token { value: Value::Sym(sym), .. }) = exp.next() { + //cond = Some(leak(sym)); + //} else { + //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + //}; + + //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { + //let path = base.as_ref().parent().unwrap().join(unquote(path)); + //if !std::fs::exists(&path)? { + //return Err(format!("(e5) not found: {path:?}").into()) + //} + //print!("layer-if:\n cond: {}\n path: {path:?}...", + //cond.unwrap_or_default()); + //let keys = read_and_leak(path)?.into(); + //let cond = cond.unwrap(); + //println!("ok"); + //map.add_layer_if( + //Box::new(move |state: &App|Take::take_or_fail( + //state, exp, ||"missing input layer conditional" + //)), keys + //); + //} else { + //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) + //} + //}, + + //_ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) + //} + //} else { + //return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) + //} + //} else { + //return Err(format!("(e1) unexpected token {token:?}").into()) + //} + //} + //Ok(map) + //} +//{ +//} + //fn from (source: &'s str) -> Self { + //// this should be for single layer: + //use DslVal::*; + //let mut layers = vec![]; + //let mut source = CstIter::from(source); + //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { + //let mut iter = iter.clone(); + //layers.push(match iter.next().map(|x|x.value) { + //Some(Sym(sym)) if sym.starts_with("@") => InputLayer { + //cond: None, + //bind: vec![[ + //dsl_val(source.nth(1).unwrap()), + //dsl_val(source.nth(2).unwrap()), + //]], + //}, + //Some(Str(layer)) => InputLayer { + //cond: None, + //bind: dsl_val(source.nth(1).unwrap()), + //}, + //Some(Key("if")) => InputLayer { + //cond: Some(dsl_val(source.nth(1).unwrap())), + //bind: dsl_val(source.nth(2).unwrap()), + //}, + //_ => panic!("invalid token in keymap"), + //}) + //} + //Self(layers) + //} +//} + +fn unquote (x: &str) -> &str { + let mut chars = x.chars(); + chars.next(); + //chars.next_back(); + chars.as_str() +} + +fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { + Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) +} + +fn leak (x: impl AsRef) -> &'static str { + Box::leak(x.as_ref().into()) } diff --git a/input/src/lib.rs b/input/src/lib.rs index 757ab88..86c6150 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,6 +1,10 @@ #![feature(associated_type_defaults)] #![feature(if_let_guard)] +pub(crate) use std::fmt::Debug; +pub(crate) use std::collections::BTreeMap; +pub(crate) use std::path::{Path, PathBuf}; +pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; mod input_macros; diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 5b3f3d5..025e522 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -3,7 +3,7 @@ use crate::{dsl::*, input::*, tui::TuiIn}; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; #[test] fn test_subcommand () -> Usually<()> { - struct Test { keys: InputLayers } + struct Test { keys: InputMap<(), Ast> } handle!(TuiIn: |self: Test, input|Ok(None));/*if let Some(command) = self.keys.command(self, input) { Ok(Some(true)) @@ -35,12 +35,12 @@ use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, Key } let mut test = Test { - keys: InputLayers::from(" + keys: InputMap::from_source(" (@a do-thing) (@b do-thing-arg 0) (@c do-sub do-other-thing) (@d do-sub do-other-thing-arg 0) - ") + ")? }; //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { From 7271081fc99a6bbecf05bbd935e9cdea25a665e2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 22:22:45 +0300 Subject: [PATCH 114/178] wip: mrr --- ] | 200 --------------------------- dsl/src/cst.rs | 115 ++++++++------- dsl/src/dsl.rs | 30 ++-- dsl/src/lib.rs | 2 - editor/Cargo.toml | 5 + vin/Cargo.toml => editor/src/main.rs | 0 input/src/input_dsl.rs | 46 ++++-- input/src/lib.rs | 3 +- tengri/src/test.rs | 8 +- tui/src/tui_engine/tui_input.rs | 6 +- 10 files changed, 119 insertions(+), 296 deletions(-) delete mode 100644 ] create mode 100644 editor/Cargo.toml rename vin/Cargo.toml => editor/src/main.rs (100%) diff --git a/] b/] deleted file mode 100644 index 93bdbbd..0000000 --- a/] +++ /dev/null @@ -1,200 +0,0 @@ -use crate::*; -/// A collection of input bind. -/// -/// Each contained layer defines a mapping from input event to command invocation -/// over a given state. Furthermore, each layer may have an associated cond, -/// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); -impl InputMap { - /// Create input layer collection from path to text file. - pub fn from_path > (path: P) -> Usually { - if !exists(path.as_ref())? { - return Err(format!("(e5) not found: {path:?}").into()) - } - Self::from_source(read_and_leak(path)?) - } - /// Create input layer collection from string. - pub fn from_source > (source: S) -> Usually { - Self::from_dsl(CstIter::from(source.as_ref())) - } - /// Create input layer collection from DSL. - pub fn from_dsl (dsl: impl Dsl) -> Usually { - use DslVal::*; - let mut input_map: Self = Self(Default::default()); - while let Exp(_, mut exp) = dsl.val() { - match exp.nth(0).map(|x|x.val()) { - Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); - let module = InputMap::::from_path(&path)?; - for (key, val) in module.0.into_iter() { - } - }, - Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { - //input_map.unconditional.push(expr); - todo!("binding"); - }, - Some(Exp(_, expr)) if expr.nth(0).map(|x|x.val()) == Some(Key("if")) => { - todo!("conditional binding"); - }, - _ => return Result::Err("invalid token in keymap".into()), - } - } - Ok(input_map) - } - /// 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: DslInto + DslInto, - O: Command - { - todo!(); - //let layers = self.0.as_slice(); - //for InputLayer { cond, bind } in layers.iter() { - //let mut matches = true; - //if let Some(cond) = cond { - //matches = state.dsl_into(cond, ||format!("input: no cond").into())?; - //} - //if matches - //&& let Some(exp) = bind.val().exp_head() - //&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? - //&& let Some(command) = state.try_dsl_into(exp)? { - //return Ok(Some(command)) - //} - //} - Ok(None) - } - /* - /// Create an input map with a single non-condal layer. - /// (Use [Default::default] to get an empty map.) - pub fn new (layer: DslVal) -> Self { - Self::default().layer(layer) - } - /// Add layer, return `Self`. - pub fn layer (mut self, layer: DslVal) -> Self { - self.add_layer(layer); self - } - /// Add condal layer, return `Self`. - pub fn layer_if (mut self, cond: DslVal, layer: DslVal) -> Self { - self.add_layer_if(Some(cond), layer); self - } - /// Add layer, return `&mut Self`. - pub fn add_layer (&mut self, layer: DslVal) -> &mut Self { - self.add_layer_if(None, layer.into()); self - } - /// Add condal layer, return `&mut Self`. - pub fn add_layer_if (&mut self, cond: Option>, bind: DslVal) -> &mut Self { - self.0.push(InputLayer { cond, bind }); - self - } - */ -} - - - - //let mut keys = iter.unwrap(); - //let mut map = InputMap::default(); - //while let Some(token) = keys.next() { - //if let Value::Exp(_, mut exp) = token.value { - //let next = exp.next(); - //if let Some(Token { value: Value::Key(sym), .. }) = next { - //match sym { - //"layer" => { - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //map.add_layer(read_and_leak(path)?.into()); - //print!("layer:\n path: {:?}...", exp.0.0.trim()); - //println!("ok"); - //} else { - //return Err(format!("(e4) unexpected non-string {next:?}").into()) - //} - //}, - - //"layer-if" => { - //let mut cond = None; - - //if let Some(Token { value: Value::Sym(sym), .. }) = exp.next() { - //cond = Some(leak(sym)); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //}; - - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //print!("layer-if:\n cond: {}\n path: {path:?}...", - //cond.unwrap_or_default()); - //let keys = read_and_leak(path)?.into(); - //let cond = cond.unwrap(); - //println!("ok"); - //map.add_layer_if( - //Box::new(move |state: &App|Take::take_or_fail( - //state, exp, ||"missing input layer conditional" - //)), keys - //); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //} - //}, - - //_ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) - //} - //} else { - //return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) - //} - //} else { - //return Err(format!("(e1) unexpected token {token:?}").into()) - //} - //} - //Ok(map) - //} -//{ -//} - //fn from (source: &'s str) -> Self { - //// this should be for single layer: - //use DslVal::*; - //let mut layers = vec![]; - //let mut source = CstIter::from(source); - //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { - //let mut iter = iter.clone(); - //layers.push(match iter.next().map(|x|x.value) { - //Some(Sym(sym)) if sym.starts_with("@") => InputLayer { - //cond: None, - //bind: vec![[ - //dsl_val(source.nth(1).unwrap()), - //dsl_val(source.nth(2).unwrap()), - //]], - //}, - //Some(Str(layer)) => InputLayer { - //cond: None, - //bind: dsl_val(source.nth(1).unwrap()), - //}, - //Some(Key("if")) => InputLayer { - //cond: Some(dsl_val(source.nth(1).unwrap())), - //bind: dsl_val(source.nth(2).unwrap()), - //}, - //_ => panic!("invalid token in keymap"), - //}) - //} - //Self(layers) - //} -//} - -fn unquote (x: &str) -> &str { - let mut chars = x.chars(); - chars.next(); - //chars.next_back(); - chars.as_str() -} - -fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { - Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) -} - -fn leak (x: impl AsRef) -> &'static str { - Box::leak(x.as_ref().into()) -} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs index a993760..984bd4e 100644 --- a/dsl/src/cst.rs +++ b/dsl/src/cst.rs @@ -5,10 +5,10 @@ use crate::*; /// CST stores strings as source references and expressions as [CstIter] instances. #[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'src>(pub CstIter<'src>); -impl<'src> Dsl for Cst<'src> { - type Str = &'src str; - type Exp = CstIter<'src>; +pub struct Cst<'s>(pub CstIter<'s>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstIter<'s>; fn nth (&self, index: usize) -> Option> { self.0.nth(index) } @@ -16,49 +16,49 @@ impl<'src> Dsl for Cst<'src> { /// Parsed substring with range and value. #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstVal<'src> { +pub struct CstVal<'s> { /// Meaning of token. - pub value: DslVal<&'src str, CstIter<'src>>, + pub value: DslVal<&'s str, CstIter<'s>>, /// Reference to source text. - pub source: &'src str, + pub source: &'s str, /// Index of 1st character of token. pub start: usize, /// Length of token. pub length: usize, } -impl<'src> Dsl for CstVal<'src> { - type Str = &'src str; - type Exp = CstIter<'src>; +impl<'s> Dsl for CstVal<'s> { + type Str = &'s str; + type Exp = CstIter<'s>; fn nth (&self, index: usize) -> Option> { todo!() } } -impl<'src> CstVal<'src> { +impl<'s> CstVal<'s> { pub const fn new ( - source: &'src str, + source: &'s str, start: usize, length: usize, - value: DslVal<&'src str, CstIter<'src>> + value: DslVal<&'s str, CstIter<'s>> ) -> Self { Self { source, start, length, value } } pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } - pub const fn slice (&'src self) -> &'src str { + pub const fn slice (&'s self) -> &'s str { self.slice_source(self.source) } - pub const fn slice_source <'range> (&'src self, source: &'range str) -> &'range str { + pub const fn slice_source <'range> (&'s self, source: &'range str) -> &'range str { str_range(source, self.start, self.end()) } - pub const fn slice_source_exp <'range> (&'src self, source: &'range str) -> &'range str { + pub const fn slice_source_exp <'range> (&'s self, source: &'range str) -> &'range str { str_range(source, self.start.saturating_add(1), self.end()) } - pub const fn with_value (self, value: DslVal<&'src str, CstIter<'src>>) -> Self { + pub const fn with_value (self, value: DslVal<&'s str, CstIter<'s>>) -> Self { Self { value, ..self } } - pub const fn value (&self) -> DslVal<&'src str, CstIter<'src>> { + pub const fn value (&self) -> DslVal<&'s str, CstIter<'s>> { self.value } pub const fn error (self, error: DslErr) -> Self { @@ -121,46 +121,37 @@ impl<'src> CstVal<'src> { /// [Cst::next] returns just the [Cst] and mutates `self`, /// instead of returning an updated version of the struct as [CstConstIter::next] does. #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'src>(pub CstConstIter<'src>); -impl<'src> Dsl for CstIter<'src> { - type Str = &'src str; +pub struct CstIter<'s>(pub CstConstIter<'s>); +impl<'s> Dsl for CstIter<'s> { + type Str = &'s str; type Exp = Self; fn nth (&self, index: usize) -> Option> { - use DslVal::*; - self.0.nth(index).map(|x|match x { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s), - Key(s) => Sym(s), - Str(s) => Sym(s), - Exp(d, x) => DslVal::Exp(d, CstIter(x)), - }) + self.0.nth(index).map(|x|dsl_val(x)) } } -impl<'src> CstIter<'src> { - pub const fn new (source: &'src str) -> Self { +impl<'s> CstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { self.0.peek() } } -impl<'src> Iterator for CstIter<'src> { - type Item = CstVal<'src>; - fn next (&mut self) -> Option> { +impl<'s> Iterator for CstIter<'s> { + type Item = CstVal<'s>; + fn next (&mut self) -> Option> { self.0.next().map(|(item, rest)|{ self.0 = rest; item }) } } -impl<'src> Into>> for CstIter<'src> { - fn into (self) -> Vec> { +impl<'s> Into>> for CstIter<'s> { + fn into (self) -> Vec> { self.collect() } } -impl<'src> Into> for CstIter<'src> { +impl<'s> Into> for CstIter<'s> { fn into (self) -> Vec { self.map(Into::into).collect() } @@ -172,9 +163,9 @@ impl<'src> Into> for CstIter<'src> { /// * the source text remaining /// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'src>(pub &'src str); -impl<'src> Dsl for CstConstIter<'src> { - type Str = &'src str; +pub struct CstConstIter<'s>(pub &'s str); +impl<'s> Dsl for CstConstIter<'s> { + type Str = &'s str; type Exp = Self; fn nth (&self, mut index: usize) -> Option> { use DslVal::*; @@ -182,37 +173,39 @@ impl<'src> Dsl for CstConstIter<'src> { for i in 0..index { iter = iter.next()?.1 } - iter.next().map(|(x, _)|match x.value { - Nil => Nil, - Err(e) => Err(e), - Num(u) => Num(u), - Sym(s) => Sym(s), - Key(s) => Sym(s), - Str(s) => Sym(s), - Exp(d, x) => DslVal::Exp(d, x.0), - }) + iter.next().map(|(x, _)|dsl_val(x.value)) } } -impl<'src> CstConstIter<'src> { - pub const fn new (source: &'src str) -> Self { +impl<'s> CstConstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(source) } pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn next (mut self) -> Option<(CstVal<'src>, Self)> { + pub const fn next (mut self) -> Option<(CstVal<'s>, Self)> { Self::next_mut(&mut self) } - pub const fn peek (&self) -> Option> { + pub const fn peek (&self) -> Option> { peek_src(self.0) } - pub const fn next_mut (&mut self) -> Option<(CstVal<'src>, Self)> { + pub const fn next_mut (&mut self) -> Option<(CstVal<'s>, Self)> { match self.peek() { Some(token) => Some((token, self.chomp(token.end()))), None => None } } } +impl<'s> From> for CstIter<'s> { + fn from (iter: CstConstIter<'s>) -> Self { + Self(iter) + } +} +impl<'s> From> for CstConstIter<'s> { + fn from (iter: CstIter<'s>) -> Self { + iter.0 + } +} /// Implement the const iterator pattern. macro_rules! const_iter { @@ -229,8 +222,8 @@ macro_rules! const_iter { } } -const_iter!(<'src>|self: CstConstIter<'src>| - => CstVal<'src> +const_iter!(<'s>|self: CstConstIter<'s>| + => CstVal<'s> => self.next_mut().map(|(result, _)|result)); /// Static iteration helper used by [cst]. @@ -244,9 +237,9 @@ macro_rules! iterate { } } -pub const fn peek_src <'src> (source: &'src str) -> Option> { +pub const fn peek_src <'s> (source: &'s str) -> Option> { use DslVal::*; - let mut token: CstVal<'src> = CstVal::new(source, 0, 0, Nil); + let mut token: CstVal<'s> = CstVal::new(source, 0, 0, Nil); iterate!(char_indices(source) => (start, c) => token = match token.value() { Err(_) => return Some(token), Nil => match c { diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index b2733e0..00f83b3 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -42,7 +42,20 @@ pub enum DslVal { Exp(usize, Exp), } -pub trait Dsl { +pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { + use DslVal::*; + match val { + 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.into()), + } +} + +pub trait Dsl: Debug { type Str: PartialEq + Clone + Default + Debug + AsRef; type Exp: PartialEq + Clone + Default + Debug + Dsl; fn nth (&self, index: usize) -> Option>; @@ -61,7 +74,7 @@ impl< fn val (&self) -> DslVal { self.clone() } - fn nth (&self, index: usize) -> Option> { + fn nth (&self, _index: usize) -> Option> { todo!() } } @@ -163,16 +176,3 @@ from_str!(Ast|source|Self::from(CstIter::from(source))); from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); from_str!(CstIter<'s>|source|Self(CstConstIter(source))); from_str!(CstConstIter<'s>|source|Self::new(source)); - -pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { - use DslVal::*; - match val { - 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.into()), - } -} diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index aa255fc..264fa19 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -5,8 +5,6 @@ pub(crate) use ::tengri_core::*; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; pub(crate) use std::collections::VecDeque; -pub(crate) use std::path::Path; -pub(crate) use std::fs::exists; pub(crate) use konst::iter::{ConstIntoIter, IsIteratorKind}; pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; diff --git a/editor/Cargo.toml b/editor/Cargo.toml new file mode 100644 index 0000000..692cf03 --- /dev/null +++ b/editor/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "tengri_editor" +description = "Embeddable editor for Tengri DSL." +version = { workspace = true } +edition = { workspace = true } diff --git a/vin/Cargo.toml b/editor/src/main.rs similarity index 100% rename from vin/Cargo.toml rename to editor/src/main.rs diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index e642490..363efc0 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,11 +1,22 @@ use crate::*; -/// A collection of input bind. +/// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation /// over a given state. Furthermore, each layer may have an associated cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap(std::collections::BTreeMap); -impl InputMap { +#[derive(Debug, Default)] pub struct InputMap( + /// Map of input event (key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> +); +#[derive(Debug, Default)] pub struct InputBinding { + condition: Option, + command: T, + description: Option>, + source: Option>, +} +impl InputMap { /// Create input layer collection from path to text file. pub fn from_path > (path: P) -> Usually { if !exists(path.as_ref())? { @@ -18,29 +29,38 @@ impl InputMap { Self::from_dsl(CstIter::from(source.as_ref())) } /// Create input layer collection from DSL. - pub fn from_dsl (dsl: impl Dsl) -> Usually { + pub fn from_dsl (dsl: D) -> Usually { use DslVal::*; - let mut input_map: Self = Self(Default::default()); - while let Exp(_, mut exp) = dsl.val() { + let mut input_map: BTreeMap>> = Default::default(); + let mut index = 0; + while let Some(Exp(_, mut exp)) = dsl.nth(index) { let val = exp.nth(0).map(|x|x.val()); match val { Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); + let path = PathBuf::from(path.as_ref()); let module = InputMap::::from_path(&path)?; for (key, val) in module.0.into_iter() { + todo!("import {exp:?} {key:?} {val:?} {path:?}"); + if !input_map.contains_key(&key) { + input_map.insert(key, vec![]); + } } }, - Some(Exp(_, expr)) if let Some(Sym(sym)) = expr.nth(0) => { - //input_map.unconditional.push(expr); - todo!("binding"); + Some(Sym(sym)) => { + //let key: I = sym.into(); + //if !input_map.contains_key(&key) { + //input_map.insert(key, vec![]); + //} + todo!("binding {exp:?} {sym:?}"); }, - Some(Exp(_, expr)) if let Some(Key(key)) = expr.nth(0) && key.as_ref() == "if" => { - todo!("conditional binding"); + Some(Key(key)) if key.as_ref() == "if" => { + todo!("conditional binding {exp:?} {key:?}"); }, _ => return Result::Err(format!("invalid token in keymap: {val:?}").into()), } + index += 1; } - Ok(input_map) + Ok(Self(input_map)) } /// Evaluate the active layers for a given state, /// returning the command to be executed, if any. diff --git a/input/src/lib.rs b/input/src/lib.rs index 86c6150..0779092 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -2,7 +2,8 @@ #![feature(if_let_guard)] pub(crate) use std::fmt::Debug; -pub(crate) use std::collections::BTreeMap; +pub(crate) use std::sync::Arc; +pub(crate) use std::collections::{BTreeMap, HashMap}; pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 025e522..c782996 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,9 +1,15 @@ use crate::*; use crate::{dsl::*, input::*, tui::TuiIn}; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; +use std::cmp::Ordering; #[test] fn test_subcommand () -> Usually<()> { - struct Test { keys: InputMap<(), Ast> } + #[derive(Debug)] struct Event(crossterm::event::Event); + impl Eq for Event {} + impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } + impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } + impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } + struct Test { keys: InputMap } handle!(TuiIn: |self: Test, input|Ok(None));/*if let Some(command) = self.keys.command(self, input) { Ok(Some(true)) diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 3bf7791..9ee6be6 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -8,13 +8,13 @@ pub struct TuiIn( /// Exit flag pub Arc, /// Input event - pub Event, + pub crossterm::event::Event, ); impl Input for TuiIn { - type Event = Event; + type Event = crossterm::event::Event; type Handled = bool; - fn event (&self) -> &Event { &self.1 } + fn event (&self) -> &crossterm::event::Event { &self.1 } fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } fn done (&self) { self.0.store(true, Relaxed); } } From 38d29f30a772438c96187c7689ed975067c1671b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 23:06:17 +0300 Subject: [PATCH 115/178] fix(proc): expose variants --- proc/src/proc_expose.rs | 92 +++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 98b0df0..d0f643b 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -60,37 +60,7 @@ impl ToTokens for ExposeImpl { let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { - let formatted_type = format!("{}", quote! { #t }); - let predefined = match formatted_type.as_str() { - "bool" => quote! { - Some(::tengri::dsl::DslVal::Sym(":true")) => true, - Some(::tengri::dsl::DslVal::Sym(":false")) => false, - }, - "u8" | "u16" | "u32" | "u64" | "usize" | - "i8" | "i16" | "i32" | "i64" | "isize" => { - let num_err = LitStr::new( - &format!("{{n}}: failed to convert to {formatted_type}"), - Span::call_site() - ); - quote! { - Some(::tengri::dsl::DslVal::Num(n)) => TryInto::<#t>::try_into(*n) - .unwrap_or_else(|_|panic!(#num_err)), - } - }, - _ => quote! {}, - }; - let values = variants.iter().map(|(key, value)|{ - let key = LitStr::new(&key, Span::call_site()); - quote! { Some(::tengri::dsl::DslVal::Sym(#key)) => state.#value(), } - }); - write_quote_to(out, quote! { - /// Generated by [tengri_proc::expose]. - 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) })) - } - } - }); + self.expose_variants(out, t, variants); } if exposed.len() > 0 { //panic!("{}", quote! {#out}); @@ -98,6 +68,66 @@ impl ToTokens for ExposeImpl { } } +impl ExposeImpl { + fn expose_variants ( + &self, out: &mut TokenStream2, t: &ExposeType, variants: &BTreeMap + ) { + let Self(ItemImpl { self_ty: state, .. }, ..) = self; + let arms = variants.iter().map(|(key, value)|{ + let key = LitStr::new(&key, Span::call_site()); + quote! { #key => state.#value(), } + }); + let arms = Self::with_predefined(t, quote! { #(#arms)* }); + write_quote_to(out, quote! { + /// Generated by [tengri_proc::expose]. + impl ::tengri::dsl::DslFrom<#state> for #t { + fn try_dsl_from (state: &#state, dsl: &impl Dsl) -> Perhaps { + Ok(Some(match dsl.val() { + #arms + _ => { return Ok(None) } + })) + } + } + }); + } + fn with_predefined (t: &ExposeType, variants: impl ToTokens) -> impl ToTokens { + let formatted_type = format!("{}", quote! { #t }); + if &formatted_type == "bool" { + return quote! { + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + ":true" => true, + ":false" => false, + #variants + _ => { return Ok(None) } + }, + } + } + if matches!(formatted_type.as_str(), + "u8" | "u16" | "u32" | "u64" | "usize" | + "i8" | "i16" | "i32" | "i64" | "isize") + { + let num_err = LitStr::new( + &format!("{{n}}: failed to convert to {formatted_type}"), + Span::call_site() + ); + return quote! { + ::tengri::dsl::DslVal::Num(n) => TryInto::<#t>::try_into(n) + .unwrap_or_else(|_|panic!(#num_err)), + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + #variants + _ => { return Ok(None) } + }, + } + } + return quote! { + ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + #variants + _ => { return Ok(None) } + }, + } + } +} + impl From for ExposeSym { fn from (this: LitStr) -> Self { Self(this) } } impl PartialOrd for ExposeSym { From ca4c558eab46aad27da145381e425908d995a625 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 14 Jul 2025 23:06:29 +0300 Subject: [PATCH 116/178] fix(input): InputMap manual Default impl --- input/src/input_dsl.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 363efc0..7ac0644 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,12 +4,17 @@ 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 cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug, Default)] pub struct InputMap( +#[derive(Debug)] pub struct InputMap( /// Map of input event (key combination) to /// all command expressions bound to it by /// all loaded input layers. pub BTreeMap>> ); +impl Default for InputMap { + fn default () -> Self { + Self(Default::default()) + } +} #[derive(Debug, Default)] pub struct InputBinding { condition: Option, command: T, From e72225f83c89a2bc9c6bee9b00373dfa530e1909 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 16 Jul 2025 00:10:03 +0300 Subject: [PATCH 117/178] wip: fix dsl --- Cargo.toml | 1 + dsl/Cargo.toml | 1 + dsl/src/ast.rs | 38 ---- dsl/src/cst.rs | 312 -------------------------------- dsl/src/dsl.rs | 178 ------------------- dsl/src/lib.rs | 371 ++++++++++++++++++++++++++++++++++++++- dsl/src/test.rs | 18 +- output/src/ops.rs | 8 +- proc/src/proc_command.rs | 6 +- proc/src/proc_expose.rs | 5 +- proc/src/proc_view.rs | 57 +++--- 11 files changed, 421 insertions(+), 574 deletions(-) delete mode 100644 dsl/src/ast.rs delete mode 100644 dsl/src/cst.rs delete mode 100644 dsl/src/dsl.rs diff --git a/Cargo.toml b/Cargo.toml index a7bb2eb..fa89dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edition = "2024" [workspace.dependencies] atomic_float = { version = "1" } better-panic = { version = "0.3.0" } +const_panic = { version = "0.2.12", features = [ "derive" ] } crossterm = { version = "0.28.1" } heck = { version = "0.5" } itertools = { version = "0.14.0" } diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index ab0bc37..256b599 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -7,6 +7,7 @@ edition = { workspace = true } [dependencies] tengri_core = { path = "../core" } konst = { workspace = true } +const_panic = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } diff --git a/dsl/src/ast.rs b/dsl/src/ast.rs deleted file mode 100644 index ac85d87..0000000 --- a/dsl/src/ast.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! The abstract syntax tree (AST) can be produced from the CST -//! by cloning source slices into owned ([Arc]) string slices. -use crate::*; - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(pub Arc, Ast>>>); - -impl Dsl for Ast { - type Str = Arc; - type Exp = Ast; - fn nth (&self, index: usize) -> Option> { - self.0.get(index).cloned() - } -} - -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstIter<'s>) -> Self { - Self(cst.map(|x|x.value.into()).collect::>().into()) - } -} - -impl<'s> From> for Ast { - fn from (cst: CstVal<'s>) -> Self { - Self(VecDeque::from([dsl_val(cst.val())]).into()) - } -} - -impl<'s> From>> for DslVal, Ast> { - fn from (cst: DslVal<&'s str, CstIter<'s>>) -> Self { - dsl_val(cst) - } -} diff --git a/dsl/src/cst.rs b/dsl/src/cst.rs deleted file mode 100644 index 984bd4e..0000000 --- a/dsl/src/cst.rs +++ /dev/null @@ -1,312 +0,0 @@ -//! The concrete syntax tree (CST) implements zero-copy -//! parsing of the DSL from a string reference. CST items -//! preserve info about their location in the source. -use crate::*; - -/// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstIter<'s>); -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index) - } -} - -/// Parsed substring with range and value. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct CstVal<'s> { - /// Meaning of token. - pub value: DslVal<&'s str, CstIter<'s>>, - /// Reference to source text. - pub source: &'s str, - /// Index of 1st character of token. - pub start: usize, - /// Length of token. - pub length: usize, -} -impl<'s> Dsl for CstVal<'s> { - type Str = &'s str; - type Exp = CstIter<'s>; - fn nth (&self, index: usize) -> Option> { - todo!() - } -} - -impl<'s> CstVal<'s> { - pub const fn new ( - source: &'s str, - start: usize, - length: usize, - value: DslVal<&'s str, CstIter<'s>> - ) -> Self { - Self { source, start, length, value } - } - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) - } - pub const fn slice (&'s self) -> &'s str { - self.slice_source(self.source) - } - pub const fn slice_source <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start, self.end()) - } - pub const fn slice_source_exp <'range> (&'s self, source: &'range str) -> &'range str { - str_range(source, self.start.saturating_add(1), self.end()) - } - pub const fn with_value (self, value: DslVal<&'s str, CstIter<'s>>) -> Self { - Self { value, ..self } - } - pub const fn value (&self) -> DslVal<&'s str, CstIter<'s>> { - self.value - } - 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: 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(DslVal::Key(token.slice_source(self.source))) - } - pub const fn grow_sym (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Sym(token.slice_source(self.source))) - } - pub const fn grow_str (self) -> Self { - let token = self.grow(); - token.with_value(DslVal::Str(token.slice_source(self.source))) - } - pub const fn grow_exp (self) -> Self { - let token = self.grow(); - if let DslVal::Exp(depth, _) = token.value() { - token.with_value(DslVal::Exp(depth, CstIter::new(token.slice_source_exp(self.source)))) - } else { - unreachable!() - } - } - pub const fn grow_in (self) -> Self { - let token = self.grow_exp(); - 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 DslVal::Exp(depth, source) = token.value() { - if depth > 0 { - token.with_value(DslVal::Exp(depth - 1, source)) - } else { - return self.error(Unexpected(')')) - } - } else { - unreachable!() - } - } -} - -/// Provides a native [Iterator] API over [CstConstIter], -/// emitting [Cst] items. -/// -/// [Cst::next] returns just the [Cst] and mutates `self`, -/// instead of returning an updated version of the struct as [CstConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'s>(pub CstConstIter<'s>); -impl<'s> Dsl for CstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, index: usize) -> Option> { - self.0.nth(index).map(|x|dsl_val(x)) - } -} -impl<'s> CstIter<'s> { - pub const fn new (source: &'s str) -> Self { - Self(CstConstIter::new(source)) - } - pub const fn peek (&self) -> Option> { - self.0.peek() - } -} -impl<'s> Iterator for CstIter<'s> { - type Item = CstVal<'s>; - fn next (&mut self) -> Option> { - self.0.next().map(|(item, rest)|{ - self.0 = rest; - item - }) - } -} -impl<'s> Into>> for CstIter<'s> { - fn into (self) -> Vec> { - self.collect() - } -} -impl<'s> Into> for CstIter<'s> { - fn into (self) -> Vec { - self.map(Into::into).collect() - } -} - -/// Owns a reference to the source text. -/// [CstConstIter::next] emits subsequent pairs of: -/// * a [Cst] and -/// * the source text remaining -/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'s>(pub &'s str); -impl<'s> Dsl for CstConstIter<'s> { - type Str = &'s str; - type Exp = Self; - fn nth (&self, mut index: usize) -> Option> { - use DslVal::*; - let mut iter = self.clone(); - for i in 0..index { - iter = iter.next()?.1 - } - iter.next().map(|(x, _)|dsl_val(x.value)) - } -} -impl<'s> CstConstIter<'s> { - pub const fn new (source: &'s str) -> Self { - Self(source) - } - pub const fn chomp (&self, index: usize) -> Self { - Self(split_at(self.0, index).1) - } - pub const fn next (mut self) -> Option<(CstVal<'s>, Self)> { - Self::next_mut(&mut self) - } - pub const fn peek (&self) -> Option> { - peek_src(self.0) - } - pub const fn next_mut (&mut self) -> Option<(CstVal<'s>, Self)> { - match self.peek() { - Some(token) => Some((token, self.chomp(token.end()))), - None => None - } - } -} -impl<'s> From> for CstIter<'s> { - fn from (iter: CstConstIter<'s>) -> Self { - Self(iter) - } -} -impl<'s> From> for CstConstIter<'s> { - fn from (iter: CstIter<'s>) -> Self { - iter.0 - } -} - -/// Implement the const iterator pattern. -macro_rules! const_iter { - ($(<$l:lifetime>)?|$self:ident: $Struct:ty| => $Item:ty => $expr:expr) => { - impl$(<$l>)? Iterator for $Struct { - type Item = $Item; - fn next (&mut $self) -> Option<$Item> { $expr } - } - impl$(<$l>)? ConstIntoIter for $Struct { - type Kind = IsIteratorKind; - type Item = $Item; - type IntoIter = Self; - } - } -} - -const_iter!(<'s>|self: CstConstIter<'s>| - => CstVal<'s> - => self.next_mut().map(|(result, _)|result)); - -/// Static iteration helper used by [cst]. -macro_rules! iterate { - ($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; - while let Some(($arg, next)) = iter.next() { - $body; - iter = next; - } - } -} - -pub const fn peek_src <'s> (source: &'s str) -> Option> { - use DslVal::*; - let mut token: CstVal<'s> = CstVal::new(source, 0, 0, Nil); - iterate!(char_indices(source) => (start, c) => token = match token.value() { - Err(_) => return Some(token), - Nil => match c { - ' '|'\n'|'\r'|'\t' => - token.grow(), - '(' => - CstVal::new(source, start, 1, Exp(1, CstIter::new(str_range(source, start, start + 1)))), - '"' => - CstVal::new(source, start, 1, Str(str_range(source, start, start + 1))), - ':'|'@' => - CstVal::new(source, start, 1, Sym(str_range(source, start, start + 1))), - '/'|'a'..='z' => - CstVal::new(source, start, 1, Key(str_range(source, start, start + 1))), - '0'..='9' => - CstVal::new(source, start, 1, match to_digit(c) { - Ok(c) => DslVal::Num(c), - Result::Err(e) => DslVal::Err(e) - }), - _ => token.error(Unexpected(c)) - }, - Str(_) => match c { - '"' => return Some(token), - _ => token.grow_str(), - }, - Num(n) => match c { - '0'..='9' => token.grow_num(n, c), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Sym(_) => match c { - 'a'..='z'|'A'..='Z'|'0'..='9'|'-' => token.grow_sym(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Key(_) => match c { - 'a'..='z'|'0'..='9'|'-'|'/' => token.grow_key(), - ' '|'\n'|'\r'|'\t'|')' => return Some(token), - _ => token.error(Unexpected(c)) - }, - Exp(depth, _) => match depth { - 0 => return Some(token.grow_exp()), - _ => match c { - ')' => token.grow_out(), - '(' => token.grow_in(), - _ => token.grow_exp(), - } - }, - }); - match token.value() { - Nil => None, - _ => Some(token), - } -} - -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) - }); - Ok(value) -} - -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) - }) -} diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs deleted file mode 100644 index 00f83b3..0000000 --- a/dsl/src/dsl.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::*; -use std::error::Error; - -/// 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")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), -} - -/// Enumeration of possible DSL tokens. -/// Generic over string and expression storage. -/// -/// * [ ] FIXME: Value may be [Err] which may shadow [Result::Err] -/// * [DslVal::Exp] wraps an expression depth and a [CstIter] -/// with the remaining part of the expression. -/// * expression depth other that 0 mean unclosed parenthesis. -/// * closing and unopened parenthesis panics during reading. -/// * [ ] TODO: signed depth might be interesting -/// * [DslVal::Sym] and [DslVal::Key] are stringish literals -/// with slightly different parsing rules. -/// * [DslVal::Num] is an unsigned integer literal. -#[derive(Clone, Debug, PartialEq, Default)] -pub enum DslVal { - #[default] - Nil, - Err(DslErr), - Num(usize), - Sym(Str), - Key(Str), - Str(Str), - Exp(usize, Exp), -} - -pub fn dsl_val , B: Into, X, Y> (val: DslVal) -> DslVal { - use DslVal::*; - match val { - 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.into()), - } -} - -pub trait Dsl: Debug { - type Str: PartialEq + Clone + Default + Debug + AsRef; - type Exp: PartialEq + Clone + Default + Debug + Dsl; - fn nth (&self, index: usize) -> Option>; - fn val (&self) -> DslVal { - self.nth(0).unwrap_or(DslVal::Nil) - } - // exp-only nth here? -} - -impl< - Str: PartialEq + Clone + Default + Debug + AsRef, - Exp: PartialEq + Clone + Default + Debug + Dsl, -> Dsl for DslVal { - type Str = Str; - type Exp = Exp; - fn val (&self) -> DslVal { - self.clone() - } - fn nth (&self, _index: usize) -> Option> { - 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<&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_exp (&self) -> Option<&Exp> { - if let Self::Exp(_, x) = self { Some(x) } else { None } - } - pub fn exp_depth (&self) -> Option { - todo!() - } - pub fn exp_head (&self) -> Option<&Self> { - todo!() - } // TODO - pub fn exp_tail (&self) -> Option<&Exp> { - todo!() - } // TODO - pub fn peek (&self) -> Option { - todo!() - } - pub fn next (&mut self) -> Option { - todo!() - } - pub fn rest (self) -> Vec { - todo!() - } -} - -impl Copy for DslVal {} - -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) - } - } -} - -macro_rules! from_str { - ($Struct:ty |$source:ident| $expr:expr) => { - impl<'s> From<&'s str> for $Struct { - fn from ($source: &'s str) -> Self { - $expr - } - } - } -} - -from_str!(Ast|source|Self::from(CstIter::from(source))); -from_str!(Cst<'s>|source|Self(CstIter(CstConstIter(source)))); -from_str!(CstIter<'s>|source|Self(CstConstIter(source))); -from_str!(CstConstIter<'s>|source|Self::new(source)); diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 264fa19..3e26227 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -1,17 +1,376 @@ #![feature(adt_const_params)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] +#![feature(const_precise_live_drops)] +extern crate const_panic; +use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; 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::DslErr::*; - -mod dsl; pub use self::dsl::*; -mod ast; pub use self::ast::*; -mod cst; pub use self::cst::*; - +pub(crate) use self::DslError::*; #[cfg(test)] mod test; + +pub type DslUsually = Result; +pub type DslPerhaps = Result, DslError>; + +/// Pronounced dizzle. +pub trait Dsl: Clone + Debug { + /// The string representation for a dizzle. + type Str: DslStr; + /// The expression representation for a dizzle. + type Exp: DslExp; + /// Return a token iterator for this dizzle. + fn dsl (&self) -> DslUsually<&Val>; +} + +/// Enumeration of values representable by a DSL [Token]s. +/// Generic over string and expression storage. +#[derive(Clone, Debug, PartialEq, Default)] +pub enum Val { + #[default] + Nil, + /// Unsigned integer literal + Num(usize), + /// Tokens that start with `:` + Sym(D::Str), + /// Tokens that don't start with `:` + Key(D::Str), + /// Quoted string literals + Str(D::Str), + /// Expressions. + Exp( + /// Expression depth checksum. Must be 0, otherwise you have an unclosed delimiter. + usize, + /// Expression content. + D::Exp + ), + Error(DslError), +} + +impl> Copy for Val {} + +impl Val { + pub fn convert (&self) -> Val where + B::Str: for<'a> From<&'a D::Str>, + B::Exp: for<'a> From<&'a D::Exp> + { + match self { Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(*u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Error(e) => Val::Error(*e) } } + pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } + pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } + pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} + pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} + pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} + pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} + pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} + pub fn exp_depth (&self) -> Option { todo!() } + pub fn exp_head_tail (&self) -> (Option<&Self>, Option<&D::Exp>) { (self.exp_head(), self.exp_tail()) } + pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO + pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } + pub fn peek (&self) -> Option { todo!() } + pub fn next (&mut self) -> Option { todo!() } + pub fn rest (self) -> Vec { todo!() } + //pub fn exp_match (&self, namespace: &str, cb: F) -> DslPerhaps + //where F: Fn(&str, &Exp)-> DslPerhaps { + //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) + //} + //} +} + +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} +impl + std::ops::Deref> DslStr for T {} + +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Default + Debug {} +impl DslExp for T {} + +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(Token); +impl Dsl for Ast { + type Str = Arc; + type Exp = VecDeque>>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// The concrete syntax tree (CST) implements zero-copy +/// parsing of the DSL from a string reference. CST items +/// preserve info about their location in the source. +/// CST stores strings as source references and expressions as [CstIter] instances. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Cst<'s>(Token>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstConstIter<'s>; + fn dsl (&self) -> DslUsually<&Val> { + Ok(self.0.value()) + } +} + +/// `State` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; + fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Options` -> [Dsl] +pub trait IntoDsl { /*TODO*/ } + +/// `self` + [Dsl] -> `Item` +pub trait DslInto { + fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } + +/// `self` + `Item` -> [Dsl] +pub trait DslFrom { /*TODO*/ } +/// Standard result type for DSL-specific operations. +pub type DslResult = Result; +/// DSL-specific error codes. +#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { + #[error("parse failed: not implemented")] + Unimplemented, + #[error("parse failed: empty")] + Empty, + #[error("parse failed: incomplete")] + Incomplete, + #[error("parse failed: unexpected character '{0}'")] + Unexpected(char), + #[error("parse failed: error #{0}")] + Code(u8), + #[error("end reached")] + End +} +/// Provides native [Iterator] API over [CstConstIter], emitting [Cst] items. +/// +/// [Cst::next] returns just the [Cst] and mutates `self`, +/// instead of returning an updated version of the struct as [CstConstIter::next] does. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstIter<'s>(CstConstIter<'s>); +impl<'s> CstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } +} +impl<'s> Iterator for CstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option { + match self.0.advance() { + Ok(Some((item, rest))) => { self.0 = rest; item.into() }, + Ok(None) => None, + Err(e) => panic!("{e:?}") + } + } +} +/// Holds a reference to the source text. +/// [CstConstIter::next] emits subsequent pairs of: +/// * a [Cst] and +/// * the source text remaining +/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct CstConstIter<'s>(pub &'s str); +impl<'s> From <&'s str> for CstConstIter<'s> { + fn from (src: &'s str) -> Self { Self(src) } +} +impl<'s> Iterator for CstConstIter<'s> { + type Item = Token>; + fn next (&mut self) -> Option>> { self.advance().unwrap().map(|x|x.0) } +} +impl<'s> ConstIntoIter for CstConstIter<'s> { + type Kind = IsIteratorKind; + type Item = Cst<'s>; + type IntoIter = Self; +} +impl<'s> CstConstIter<'s> { + pub const fn new (source: &'s str) -> Self { Self(source) } + pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } + pub const fn peek (&self) -> DslPerhaps>> { Token::peek(self.0) } + //pub const fn next (mut self) -> Option<(Token>, Self)> { + //Self::advance(&mut self).unwrap() } + pub const fn advance (&mut self) -> DslPerhaps<(Token>, Self)> { + match self.peek() { + Ok(Some(token)) => { + let end = self.chomp(token.span.end()); + Ok(Some((token.copy(), end))) + }, + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } +} + +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct Span { + /// Reference to source text. + pub source: D::Str, + /// Index of 1st character of span. + pub start: usize, + /// Length of span. + pub length: usize, +} +impl<'s, D: Dsl> Span { + pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } +} +impl<'s, D: Dsl> Span { + pub const fn slice (&self) -> &'s str { + str_range(self.source, self.start, self.end()) } + pub const fn slice_exp (&self) -> &'s str { + str_range(self.source, self.start.saturating_add(1), self.end()) } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { + if self.length + self.start >= self.source.len() { return Err(End) } + self.length = self.length.saturating_add(1); + Ok(self) } +} + +/// Parsed substring with range and value. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Token { + /// Source span of token. + span: Span, + /// Meaning of token. + value: Val, +} +impl Token { + pub const fn value (&self) -> &Val { &self.value } + pub const fn span (&self) -> &Span { &self.span } + pub const fn err (&self) -> Option { + if let Val::Error(e) = self.value { Some(e) } else { None } } + pub const fn new (source: D::Str, start: usize, length: usize, value: Val) -> Self { + Self { value, span: Span { source, start, length } } } + pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy { + Self { span: Span { ..self.span }, value: self.value } } +} +const fn or_panic (result: DslUsually) -> T { + match result { Ok(t) => t, Err(e) => const_panic::concat_panic!(e) } +} +impl<'s, D: Dsl> Token { + pub const fn peek (src: D::Str) -> DslPerhaps where D::Exp: From<&'s str> { + use Val::*; + let mut t = Self::new(src, 0, 0, Nil); + let mut iter = char_indices(src); + while let Some(((i, c), next)) = iter.next() { + t = match (t.value(), c) { + (Error(_), _) => + return Ok(Some(t)), + (Nil, ' '|'\n'|'\r'|'\t') => + *or_panic(t.grow()), + (Nil, '(') => + Self::new(src, i, 1, Exp(1, D::Exp::from(str_range(src, i, i + 1)))), + (Nil, '"') => + Self::new(src, i, 1, Str(str_range(src, i, i + 1))), + (Nil, ':'|'@') => + Self::new(src, i, 1, Sym(str_range(src, i, i + 1))), + (Nil, '/'|'a'..='z') => + Self::new(src, i, 1, Key(str_range(src, i, i + 1))), + (Nil, '0'..='9') => + Self::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), + (Nil, _) => + { t.value = Val::Error(Unexpected(c)); t }, + (Str(_), '"') => + return Ok(Some(t)), + (Str(_), _) => + { or_panic(t.grow()); t.value = Str(t.span.slice()); t }, + (Num(m), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Num(m), _) => match to_digit(c) { + Ok(n) => { t.grow()?; t.value = Num(10*m+n); t }, + Err(e) => { t.grow()?; t.value = Error(e); t } }, + (Sym(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Sym(_), 'a'..='z'|'A'..='Z'|'0'..='9'|'-') => { + t.grow()?; t.value = Sym(t.span.slice()); t }, + (Sym(_), _) => + { t.value = Error(Unexpected(c)); t }, + (Key(_), ' '|'\n'|'\r'|'\t'|')') => + return Ok(Some(t)), + (Key(_), 'a'..='z'|'0'..='9'|'-'|'/') => + { t.grow()?; t.value = Key(t.span.slice()); t }, + (Key(_), _ ) => + { t.value = Error(Unexpected(c)); t }, + (Exp(0, _), _) => + { t.grow()?; t.value = Exp(0, D::Exp::from(t.span.slice_exp())); return Ok(Some(t)) }, + (Exp(d, _), ')') => + { t.grow()?; t.value = Exp(d-1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), '(') => + { t.grow()?; t.value = Exp(d+1, D::Exp::from(t.span.slice_exp())); t }, + (Exp(d, _), _ ) => + { t.grow()?; t.value = Exp(*d, D::Exp::from(t.span.slice_exp())); t }, + }; + iter = next; + } + Ok(match t.value() { + Nil => None, + _ => Some(t) + }) + } + pub const fn grow (&mut self) -> DslUsually<&mut Self> { self.span.grow()?; Ok(self) } + pub const fn grow_exp (&mut self, d: isize) -> &mut Self where D::Exp: From<&'s str> { + if let Val::Exp(depth, _) = self.value() { + self.value = Val::Exp((*depth as isize + d) as usize, D::Exp::from(self.span.slice_exp())); + self + } else { + unreachable!() + } + } +} + +pub const fn to_digit (c: char) -> DslResult { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Result::Err(Unexpected(c)) }) } + +macro_rules! iterate(($expr:expr => $arg: pat => $body:expr) => { + let mut iter = $expr; while let Some(($arg, next)) = iter.next() { $body; iter = next; } }); +pub const fn to_number (digits: &str) -> DslResult { + let mut value = 0; + iterate!(char_indices(digits) => (_, c) => match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Result::Err(e) => return Result::Err(e) }); + Ok(value) } + +/// Implement type conversions. +macro_rules! from(($($Struct:ty { $( + $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr +);+ $(;)? })*) => { $( + $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { + fn from ($source: $From) -> Self { $expr } + })+ +)* }); + +//from! { + ////Vec> { <'s> (val: CstIter<'s>) val.collect(); } + //CstConstIter<'s> { + //<'s> (src: &'s str) Self(src); + //<'s> (iter: CstIter<'s>) iter.0; } + //CstIter<'s> { + //<'s> (src: &'s str) Self(CstConstIter(src)); + //<'s> (iter: CstConstIter<'s>) Self(iter); } + //Cst<'s> { <'s> (src: &'s str) Self(CstIter(CstConstIter(src))); } + //Vec { <'s> (val: CstIter<'s>) val.map(Into::into).collect(); } + //Token> { <'s> (token: Token>) Self { value: token.value.into(), span: token.span.into() } } + //Ast { + //<'s> (src: &'s str) Ast::from(CstIter(CstConstIter(src))); + //<'s> (cst: Cst<'s>) Ast(VecDeque::from([dsl_val(cst.val())]).into()); + //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); + // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } +//} diff --git a/dsl/src/test.rs b/dsl/src/test.rs index b275573..dfb37f4 100644 --- a/dsl/src/test.rs +++ b/dsl/src/test.rs @@ -12,10 +12,10 @@ use proptest::prelude::*; } #[test] fn test_num () { - let _digit = to_digit('0'); - let _digit = to_digit('x'); - let _number = to_number(&"123"); - let _number = to_number(&"12asdf3"); + let _digit = Token::to_digit('0'); + let _digit = Token::to_digit('x'); + let _number = Token::to_number(&"123"); + let _number = Token::to_number(&"12asdf3"); } //proptest! { //#[test] fn proptest_source_iter ( @@ -48,15 +48,15 @@ use proptest::prelude::*; //} #[test] fn test_token () -> Result<(), Box> { - use crate::DslVal::*; + use crate::Val::*; let source = ":f00"; - let mut token = CstVal::new(source, 0, 1, Sym(":")); + let mut token = CstToken::new(source, 0, 1, Sym(":")); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 2, Sym(":f"))); + assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 3, Sym(":f0"))); + assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); token = token.grow_sym(); - assert_eq!(token, CstVal::new(source, 0, 4, Sym(":f00"))); + assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); assert_eq!(None, CstIter::new("").next()); assert_eq!(None, CstIter::new(" \n \r \t ").next()); diff --git a/output/src/ops.rs b/output/src/ops.rs index 9c528f2..13ade31 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -380,7 +380,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ [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 +/// Enabling the `dsl` feature implements [FromDsl] for /// the layout elements that are provided by this crate. #[cfg(feature = "dsl")] mod ops_dsl { use crate::*; @@ -391,7 +391,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //($Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$($arg:ident $(:$ty:ty)?),*] $expr:expr) //)*) => { //$( - //impl DslFrom for $Struct$(<$($A),+>)? { + //impl FromDsl for $Struct$(<$($A),+>)? { //fn try_dsl_from ( //state: &S, dsl: &impl Dsl //) -> Perhaps { @@ -407,7 +407,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { @@ -422,7 +422,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $Struct:ident $(<$($A:ident),+>)? $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { - impl DslFrom for $Struct$(<$($A),+>)? { + impl FromDsl for $Struct$(<$($A),+>)? { fn try_dsl_from ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 06b184e..132d867 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: DslFrom::dsl_from(self, words, ||"command error")?, + #arg: FromDsl::from_dsl(self, words, ||"command error")?, }); } out @@ -149,8 +149,8 @@ impl ToTokens for CommandDef { } } /// Generated by [tengri_proc::command]. - impl ::tengri::dsl::DslFrom<#state> for #command_enum { - fn try_dsl_from ( + impl ::tengri::dsl::FromDsl<#state> for #command_enum { + fn try_from_dsl ( state: &#state, value: &impl ::tengri::dsl::Dsl ) -> Perhaps { diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index d0f643b..ca5c9e7 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -57,7 +57,6 @@ impl ToTokens for ExposeDef { impl ToTokens for ExposeImpl { fn to_tokens (&self, out: &mut TokenStream2) { let Self(block, exposed) = self; - let state = &block.self_ty; write_quote_to(out, quote! { #block }); for (t, variants) in exposed.iter() { self.expose_variants(out, t, variants); @@ -80,8 +79,8 @@ impl ExposeImpl { let arms = Self::with_predefined(t, quote! { #(#arms)* }); write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::DslFrom<#state> for #t { - fn try_dsl_from (state: &#state, dsl: &impl Dsl) -> Perhaps { + impl ::tengri::dsl::FromDsl<#state> for #t { + fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { Ok(Some(match dsl.val() { #arms _ => { return Ok(None) } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 12012c3..0f377de 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -39,26 +39,22 @@ impl Parse for ViewImpl { impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; - let self_ty = &block.self_ty; - // 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::DslVal::Exp(_, expr) => return Ok(Some( - #builtin::dsl_from(self, expr, ||Box::new("failed to load builtin".into()))? - .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::DslVal::Sym(#key) => return Ok(Some( - self.#value().boxed() - )), - })); + let Self(_, ViewImpl { block, .. }) = self; + let generated = self.generated(); write_quote_to(out, quote! { - // Original user-taked implementation: #block + #generated + }) + } +} + +impl ViewDef { + fn generated (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let self_ty = &block.self_ty; + let builtins = self.builtins(); + let exposed = self.exposed(); + quote! { /// Generated by [tengri_proc]. /// /// Makes [#self_ty] able to construct the [Render]able @@ -70,11 +66,30 @@ impl ToTokens for ViewDef { fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { - use ::tengri::dsl::DslVal::*; - Ok(match dsl.val() { #(#builtin)* #(#exposed)* _ => return Ok(None) }) + Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) } } - }) + } + } + /// Expressions are handled by built-in functions + /// that operate over constants and symbols. + fn builtins (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { + ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( + #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? + .boxed() + )), + }); + quote! { #(#builtins)* } + } + /// Symbols are handled by user-taked functions that take no parameters but `&self`. + fn exposed (&self) -> impl ToTokens { + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { + #key => return Ok(Some(self.#value().boxed())), + })); + quote! { ::tengri::dsl::DslVal::Sym(key) => match key.as_ref() { #(#exposed)* } } } } From d99b20c99da0ed6ec523a81ccb404c0479bbc228 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 17 Jul 2025 18:50:42 +0300 Subject: [PATCH 118/178] wip: fix(dsl): kinda patch it up --- dsl/src/lib.rs | 425 +++++++++++++++++++++-------------------- input/src/input_dsl.rs | 61 +++--- output/src/ops.rs | 6 +- 3 files changed, 253 insertions(+), 239 deletions(-) diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 3e26227..416db82 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -14,72 +14,135 @@ pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; #[cfg(test)] mod test; - -pub type DslUsually = Result; -pub type DslPerhaps = Result, DslError>; - -/// Pronounced dizzle. +/// A DSL expression. Generic over string and expression storage. pub trait Dsl: Clone + Debug { /// The string representation for a dizzle. type Str: DslStr; /// The expression representation for a dizzle. type Exp: DslExp; - /// Return a token iterator for this dizzle. - fn dsl (&self) -> DslUsually<&Val>; + /// Request the top-level DSL [Val]ue. + /// May perform cloning or parsing. + fn dsl (&self) -> Val; + fn err (&self) -> Option {self.dsl().err()} + fn nil (&self) -> bool {self.dsl().nil()} + fn num (&self) -> Option {self.dsl().num()} + fn sym (&self) -> Option {self.dsl().sym()} + fn key (&self) -> Option {self.dsl().key()} + fn str (&self) -> Option {self.dsl().str()} + fn exp (&self) -> Option {self.dsl().exp()} } - -/// Enumeration of values representable by a DSL [Token]s. -/// Generic over string and expression storage. +/// Enumeration of values that may figure in an expression. +/// Generic over [Dsl] implementation. #[derive(Clone, Debug, PartialEq, Default)] pub enum Val { - #[default] - Nil, + /// Empty expression + #[default] Nil, /// Unsigned integer literal Num(usize), - /// Tokens that start with `:` + /// An identifier that starts with `.` Sym(D::Str), - /// Tokens that don't start with `:` + /// An identifier that doesn't start with `:` Key(D::Str), - /// Quoted string literals + /// A quoted string literal Str(D::Str), - /// Expressions. + /// A sub-expression. Exp( /// Expression depth checksum. Must be 0, otherwise you have an unclosed delimiter. - usize, + isize, /// Expression content. D::Exp ), + /// An error. Error(DslError), } - -impl> Copy for Val {} - impl Val { - pub fn convert (&self) -> Val where - B::Str: for<'a> From<&'a D::Str>, - B::Exp: for<'a> From<&'a D::Exp> - { - match self { Val::Nil => Val::Nil, - Val::Num(u) => Val::Num(*u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(*d, x.into()), - Val::Error(e) => Val::Error(*e) } } - pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } - pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } - pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} - pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} - pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} - pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} - pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} - pub fn exp_depth (&self) -> Option { todo!() } + pub fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} + pub fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} + pub fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} + pub fn sym (&self) -> Option {match self{Val::Sym(s)=>Some(s.clone()), _=>None}} + pub fn key (&self) -> Option {match self{Val::Key(k)=>Some(k.clone()), _=>None}} + pub fn str (&self) -> Option {match self{Val::Str(s)=>Some(s.clone()), _=>None}} + pub fn exp (&self) -> Option {match self{Val::Exp(_, x)=>Some(x.clone()),_=>None}} +} +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Ast(Arc>>>); +impl Dsl for Ast { + type Str = Arc; + type Exp = Arc>>>; + fn dsl (&self) -> Val { Val::Exp(0, self.0.clone()) } +} +/// The concrete syntax tree (CST) implements zero-copy +/// parsing of the DSL from a string reference. CST items +/// preserve info about their location in the source. +/// CST stores strings as source references and expressions as [CstIter] instances. +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub struct Cst<'s>(CstConstIter<'s>); +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = CstConstIter<'s>; + fn dsl (&self) -> Val { Val::Exp(0, self.0) } +} +impl<'s> From<&'s str> for Cst<'s> { + fn from (source: &'s str) -> Self { + Self(CstConstIter(source)) + } +} +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} +impl + std::ops::Deref> DslStr for T {} +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Default + Debug { + fn head (&self) -> Val; + fn tail (&self) -> Self; +} +impl DslExp for Arc>>> { + fn head (&self) -> Val { + self.get(0).cloned().unwrap_or_else(||Arc::new(Default::default())).value.into() + } + fn tail (&self) -> Self { + Self::new(self.iter().skip(1).cloned().collect()) + } +} +impl<'s> DslExp for CstConstIter<'s> { + fn head (&self) -> Val { + peek(self.0).value.into() + } + fn tail (&self) -> Self { + let Token { span: Span { start, length, source }, .. } = peek(self.0); + Self(&source[(start+length)..]) + } +} +impl + Copy> Copy for Val {} +impl Val { + pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } + pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } + pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} + pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} + pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} + pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} + //pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} + pub fn exp_depth (&self) -> Option { todo!() } pub fn exp_head_tail (&self) -> (Option<&Self>, Option<&D::Exp>) { (self.exp_head(), self.exp_tail()) } - pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO - pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } - pub fn peek (&self) -> Option { todo!() } - pub fn next (&mut self) -> Option { todo!() } - pub fn rest (self) -> Vec { todo!() } + pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO + pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } + pub fn peek (&self) -> Option { todo!() } + pub fn next (&mut self) -> Option { todo!() } + pub fn rest (self) -> Vec { todo!() } + //pub fn convert (&self) -> Val where + //T::Str: for<'a> From<&'a D::Str>, + //T::Exp: for<'a> From<&'a D::Exp> + //{ + //match self { Val::Nil => Val::Nil, + //Val::Num(u) => Val::Num(*u), + //Val::Sym(s) => Val::Sym(s.into()), + //Val::Key(s) => Val::Key(s.into()), + //Val::Str(s) => Val::Str(s.into()), + //Val::Exp(d, x) => Val::Exp(*d, x.into()), + //Val::Error(e) => Val::Error(*e) } } //pub fn exp_match (&self, namespace: &str, cb: F) -> DslPerhaps //where F: Fn(&str, &Exp)-> DslPerhaps { //if let Some(Self::Key(key)) = self.exp_head() @@ -92,61 +155,6 @@ impl Val { //} } -/// The string representation for a [Dsl] implementation. -/// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} -impl + std::ops::Deref> DslStr for T {} - -/// The expression representation for a [Dsl] implementation. -/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. -pub trait DslExp: PartialEq + Clone + Default + Debug {} -impl DslExp for T {} - -/// The abstract syntax tree (AST) can be produced from the CST -/// by cloning source slices into owned ([Arc]) string slices. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(Token); -impl Dsl for Ast { - type Str = Arc; - type Exp = VecDeque>>; - fn dsl (&self) -> DslUsually<&Val> { - Ok(self.0.value()) - } -} - -/// The concrete syntax tree (CST) implements zero-copy -/// parsing of the DSL from a string reference. CST items -/// preserve info about their location in the source. -/// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Cst<'s>(Token>); -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = CstConstIter<'s>; - fn dsl (&self) -> DslUsually<&Val> { - Ok(self.0.value()) - } -} - -/// `State` + [Dsl] -> `Self`. -pub trait FromDsl: Sized { - fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; - fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } - -/// `self` + `Options` -> [Dsl] -pub trait IntoDsl { /*TODO*/ } - -/// `self` + [Dsl] -> `Item` -pub trait DslInto { - fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } } - -/// `self` + `Item` -> [Dsl] -pub trait DslFrom { /*TODO*/ } -/// Standard result type for DSL-specific operations. -pub type DslResult = Result; /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { #[error("parse failed: not implemented")] @@ -175,9 +183,8 @@ impl<'s> Iterator for CstIter<'s> { type Item = Token>; fn next (&mut self) -> Option { match self.0.advance() { - Ok(Some((item, rest))) => { self.0 = rest; item.into() }, - Ok(None) => None, - Err(e) => panic!("{e:?}") + Some((item, rest)) => { self.0 = rest; item.into() }, + None => None, } } } @@ -193,7 +200,7 @@ impl<'s> From <&'s str> for CstConstIter<'s> { } impl<'s> Iterator for CstConstIter<'s> { type Item = Token>; - fn next (&mut self) -> Option>> { self.advance().unwrap().map(|x|x.0) } + fn next (&mut self) -> Option>> { self.advance().map(|x|x.0) } } impl<'s> ConstIntoIter for CstConstIter<'s> { type Kind = IsIteratorKind; @@ -203,21 +210,98 @@ impl<'s> ConstIntoIter for CstConstIter<'s> { impl<'s> CstConstIter<'s> { pub const fn new (source: &'s str) -> Self { Self(source) } pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn peek (&self) -> DslPerhaps>> { Token::peek(self.0) } - //pub const fn next (mut self) -> Option<(Token>, Self)> { - //Self::advance(&mut self).unwrap() } - pub const fn advance (&mut self) -> DslPerhaps<(Token>, Self)> { - match self.peek() { - Ok(Some(token)) => { + pub const fn advance (&mut self) -> Option<(Token>, Self)> { + match peek(self.0) { + Token { value: Val::Nil, .. } => None, + token => { let end = self.chomp(token.span.end()); - Ok(Some((token.copy(), end))) + Some((token.copy(), end)) }, - Ok(None) => Ok(None), - Err(e) => Err(e) } } } +const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } +const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } +const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } +const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } +const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } +const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } +const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +const fn is_str_start (c: char) -> bool { matches!(c, '"') } +const fn is_str_end (c: char) -> bool { matches!(c, '"') } +const fn is_exp_start (c: char) -> bool { matches!(c, '(') } + +pub const fn peek <'s> (src: &'s str) -> Token> { + use Val::*; + let mut t = Token { value: Val::Nil, span: Span { source: src, start: 0, length: 0 } }; + let mut iter = char_indices(src); + while let Some(((i, c), next)) = iter.next() { + t = match (t.value(), c) { + (Error(_), _) => return t, + + (Nil, _) if is_exp_start(c) => Token::new(src, i, 1, Exp(1, CstConstIter(str_range(src, i, i+1)))), + (Nil, _) if is_str_start(c) => Token::new(src, i, 1, Str(str_range(src, i, i+1))), + (Nil, _) if is_sym_start(c) => Token::new(src, i, 1, Sym(str_range(src, i, i+1))), + (Nil, _) if is_key_start(c) => Token::new(src, i, 1, Key(str_range(src, i, i+1))), + (Nil, _) if is_digit(c) => Token::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), + (Nil, _) if is_whitespace(c) => t.grown(), + (Nil, _) => { t.value = Val::Error(Unexpected(c)); t }, + + (Str(_), _) if is_str_end(c) => return t, + (Str(_), _) => { t.value = Str(t.span.grow().slice()); t }, + + (Sym(_), _) if is_sym_end(c) => return t, + (Sym(_), _) if is_sym_char(c) => { t.value = Sym(t.span.grow().slice()); t }, + (Sym(_), _) => { t.value = Error(Unexpected(c)); t }, + + (Key(_), _) if is_key_end(c) => return t, + (Key(_), _) if is_key_char(c) => { t.value = Key(t.span.grow().slice()); t }, + (Key(_), _) => { t.value = Error(Unexpected(c)); t }, + + (Exp(0, _), _) => { t.value = Exp(0, CstConstIter(t.span.grow().slice_exp())); return t }, + (Exp(d, _), ')') => { t.value = Exp((*d)-1, CstConstIter(t.span.grow().slice_exp())); t }, + (Exp(d, _), '(') => { t.value = Exp((*d)+1, CstConstIter(t.span.grow().slice_exp())); t }, + (Exp(d, _), _ ) => { t.value = Exp(*d, CstConstIter(t.span.grow().slice_exp())); t }, + + (Num(m), _) if is_num_end(c) => return t, + (Num(m), _) => match to_digit(c) { + Ok(n) => { let m = *m; t.span.grow(); t.value = Num(n+10*m); t }, + Err(e) => { t.span.grow(); t.value = Error(e); t } }, + }; + iter = next; + } + t +} +pub const fn to_number (digits: &str) -> Result { + let mut iter = char_indices(digits); + let mut value = 0; + while let Some(((_, c), next)) = iter.next() { + match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Err(e) => return Err(e), + } + iter = next; + } + Ok(value) +} +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Err(Unexpected(c)) + }) +} +/// Parsed substring with range and value. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Token { + /// Source span of token. + span: Span, + /// Meaning of token. + value: Val, +} #[derive(Debug, Copy, Clone, Default, PartialEq)] pub struct Span { /// Reference to source text. @@ -235,20 +319,13 @@ impl<'s, D: Dsl> Span { str_range(self.source, self.start, self.end()) } pub const fn slice_exp (&self) -> &'s str { str_range(self.source, self.start.saturating_add(1), self.end()) } - pub const fn grow (&mut self) -> DslUsually<&mut Self> { - if self.length + self.start >= self.source.len() { return Err(End) } - self.length = self.length.saturating_add(1); - Ok(self) } + pub const fn grow (&mut self) -> &mut Self { + let max_length = self.source.len().saturating_sub(self.start); + self.length = self.length + 1; + if self.length > max_length { self.length = max_length } + self } } -/// Parsed substring with range and value. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Token { - /// Source span of token. - span: Span, - /// Meaning of token. - value: Val, -} impl Token { pub const fn value (&self) -> &Val { &self.value } pub const fn span (&self) -> &Span { &self.span } @@ -256,76 +333,14 @@ impl Token { if let Val::Error(e) = self.value { Some(e) } else { None } } pub const fn new (source: D::Str, start: usize, length: usize, value: Val) -> Self { Self { value, span: Span { source, start, length } } } - pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy { + pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy, Val: Copy { Self { span: Span { ..self.span }, value: self.value } } } -const fn or_panic (result: DslUsually) -> T { - match result { Ok(t) => t, Err(e) => const_panic::concat_panic!(e) } -} -impl<'s, D: Dsl> Token { - pub const fn peek (src: D::Str) -> DslPerhaps where D::Exp: From<&'s str> { - use Val::*; - let mut t = Self::new(src, 0, 0, Nil); - let mut iter = char_indices(src); - while let Some(((i, c), next)) = iter.next() { - t = match (t.value(), c) { - (Error(_), _) => - return Ok(Some(t)), - (Nil, ' '|'\n'|'\r'|'\t') => - *or_panic(t.grow()), - (Nil, '(') => - Self::new(src, i, 1, Exp(1, D::Exp::from(str_range(src, i, i + 1)))), - (Nil, '"') => - Self::new(src, i, 1, Str(str_range(src, i, i + 1))), - (Nil, ':'|'@') => - Self::new(src, i, 1, Sym(str_range(src, i, i + 1))), - (Nil, '/'|'a'..='z') => - Self::new(src, i, 1, Key(str_range(src, i, i + 1))), - (Nil, '0'..='9') => - Self::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), - (Nil, _) => - { t.value = Val::Error(Unexpected(c)); t }, - (Str(_), '"') => - return Ok(Some(t)), - (Str(_), _) => - { or_panic(t.grow()); t.value = Str(t.span.slice()); t }, - (Num(m), ' '|'\n'|'\r'|'\t'|')') => - return Ok(Some(t)), - (Num(m), _) => match to_digit(c) { - Ok(n) => { t.grow()?; t.value = Num(10*m+n); t }, - Err(e) => { t.grow()?; t.value = Error(e); t } }, - (Sym(_), ' '|'\n'|'\r'|'\t'|')') => - return Ok(Some(t)), - (Sym(_), 'a'..='z'|'A'..='Z'|'0'..='9'|'-') => { - t.grow()?; t.value = Sym(t.span.slice()); t }, - (Sym(_), _) => - { t.value = Error(Unexpected(c)); t }, - (Key(_), ' '|'\n'|'\r'|'\t'|')') => - return Ok(Some(t)), - (Key(_), 'a'..='z'|'0'..='9'|'-'|'/') => - { t.grow()?; t.value = Key(t.span.slice()); t }, - (Key(_), _ ) => - { t.value = Error(Unexpected(c)); t }, - (Exp(0, _), _) => - { t.grow()?; t.value = Exp(0, D::Exp::from(t.span.slice_exp())); return Ok(Some(t)) }, - (Exp(d, _), ')') => - { t.grow()?; t.value = Exp(d-1, D::Exp::from(t.span.slice_exp())); t }, - (Exp(d, _), '(') => - { t.grow()?; t.value = Exp(d+1, D::Exp::from(t.span.slice_exp())); t }, - (Exp(d, _), _ ) => - { t.grow()?; t.value = Exp(*d, D::Exp::from(t.span.slice_exp())); t }, - }; - iter = next; - } - Ok(match t.value() { - Nil => None, - _ => Some(t) - }) - } - pub const fn grow (&mut self) -> DslUsually<&mut Self> { self.span.grow()?; Ok(self) } +impl<'s, D: Dsl>> Token { + pub const fn grown (mut self) -> Self { self.span.grow(); self } pub const fn grow_exp (&mut self, d: isize) -> &mut Self where D::Exp: From<&'s str> { if let Val::Exp(depth, _) = self.value() { - self.value = Val::Exp((*depth as isize + d) as usize, D::Exp::from(self.span.slice_exp())); + self.value = Val::Exp(*depth as isize + d, CstConstIter(self.span.slice_exp())); self } else { unreachable!() @@ -333,20 +348,22 @@ impl<'s, D: Dsl> Token { } } -pub const fn to_digit (c: char) -> DslResult { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Result::Err(Unexpected(c)) }) } - -macro_rules! iterate(($expr:expr => $arg: pat => $body:expr) => { - let mut iter = $expr; while let Some(($arg, next)) = iter.next() { $body; iter = next; } }); -pub const fn to_number (digits: &str) -> DslResult { - let mut value = 0; - iterate!(char_indices(digits) => (_, c) => match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Result::Err(e) => return Result::Err(e) }); - Ok(value) } +/// `State` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; + fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } +} +/// `self` + `Options` -> [Dsl] +pub trait IntoDsl { /*TODO*/ } +/// `self` + [Dsl] -> `Item` +pub trait DslInto { + fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } +} +/// `self` + `Item` -> [Dsl] +pub trait DslFrom { /*TODO*/ } /// Implement type conversions. macro_rules! from(($($Struct:ty { $( diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 7ac0644..882612d 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,24 +4,24 @@ 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 cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug)] pub struct InputMap( +#[derive(Debug)] pub struct InputMap( /// Map of input event (key combination) to /// all command expressions bound to it by /// all loaded input layers. - pub BTreeMap>> + pub BTreeMap>> ); -impl Default for InputMap { +impl Default for InputMap { fn default () -> Self { Self(Default::default()) } } -#[derive(Debug, Default)] pub struct InputBinding { - condition: Option, - command: T, +#[derive(Debug, Default)] pub struct InputBinding { + condition: Option, + command: D, description: Option>, source: Option>, } -impl InputMap { +impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { /// Create input layer collection from path to text file. pub fn from_path > (path: P) -> Usually { if !exists(path.as_ref())? { @@ -30,22 +30,19 @@ impl InputMap { Self::from_source(read_and_leak(path)?) } /// Create input layer collection from string. - pub fn from_source > (source: S) -> Usually { - Self::from_dsl(CstIter::from(source.as_ref())) + pub fn from_source (source: impl AsRef) -> Usually { + Self::from_dsl(D::from(Cst::from(source.as_ref()))) } /// Create input layer collection from DSL. - pub fn from_dsl (dsl: D) -> Usually { - use DslVal::*; - let mut input_map: BTreeMap>> = Default::default(); - let mut index = 0; - while let Some(Exp(_, mut exp)) = dsl.nth(index) { - let val = exp.nth(0).map(|x|x.val()); - match val { + pub fn from_dsl (dsl: D) -> Usually { + use Val::*; + let mut input_map: BTreeMap>> = Default::default(); + match dsl.exp() { + Some(exp) => match exp.head() { Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); - let module = InputMap::::from_path(&path)?; - for (key, val) in module.0.into_iter() { - todo!("import {exp:?} {key:?} {val:?} {path:?}"); + let path = PathBuf::from(path.as_ref()); + for (key, val) in InputMap::::from_path(&path)?.0.into_iter() { + todo!("import {path:?} {key:?} {val:?}"); if !input_map.contains_key(&key) { input_map.insert(key, vec![]); } @@ -56,14 +53,14 @@ impl InputMap { //if !input_map.contains_key(&key) { //input_map.insert(key, vec![]); //} - todo!("binding {exp:?} {sym:?}"); + todo!("binding {sym:?} {:?}", exp.tail()); }, - Some(Key(key)) if key.as_ref() == "if" => { - todo!("conditional binding {exp:?} {key:?}"); + Some(Key("if")) => { + todo!("conditional binding {:?}", exp.tail()); }, - _ => return Result::Err(format!("invalid token in keymap: {val:?}").into()), - } - index += 1; + _ => return Err(format!("invalid form in keymap: {exp:?}").into()) + }, + _ => return Err(format!("not an expression: {dsl:?}").into()) } Ok(Self(input_map)) } @@ -92,23 +89,23 @@ impl InputMap { /* /// Create an input map with a single non-condal layer. /// (Use [Default::default] to get an empty map.) - pub fn new (layer: DslVal) -> Self { + pub fn new (layer: Val) -> Self { Self::default().layer(layer) } /// Add layer, return `Self`. - pub fn layer (mut self, layer: DslVal) -> Self { + pub fn layer (mut self, layer: Val) -> Self { self.add_layer(layer); self } /// Add condal layer, return `Self`. - pub fn layer_if (mut self, cond: DslVal, layer: DslVal) -> Self { + pub fn layer_if (mut self, cond: Val, layer: Val) -> Self { self.add_layer_if(Some(cond), layer); self } /// Add layer, return `&mut Self`. - pub fn add_layer (&mut self, layer: DslVal) -> &mut Self { + pub fn add_layer (&mut self, layer: Val) -> &mut Self { self.add_layer_if(None, layer.into()); self } /// Add condal layer, return `&mut Self`. - pub fn add_layer_if (&mut self, cond: Option>, bind: DslVal) -> &mut Self { + pub fn add_layer_if (&mut self, cond: Option>, bind: Val) -> &mut Self { self.0.push(InputLayer { cond, bind }); self } @@ -182,7 +179,7 @@ impl InputMap { //} //fn from (source: &'s str) -> Self { //// this should be for single layer: - //use DslVal::*; + //use Val::*; //let mut layers = vec![]; //let mut source = CstIter::from(source); //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { diff --git a/output/src/ops.rs b/output/src/ops.rs index 13ade31..976ab51 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -392,7 +392,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //)*) => { //$( //impl FromDsl for $Struct$(<$($A),+>)? { - //fn try_dsl_from ( + //fn try_from_dsl ( //state: &S, dsl: &impl Dsl //) -> Perhaps { //todo!() @@ -408,7 +408,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_dsl_from ( + fn try_from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() @@ -423,7 +423,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_dsl_from ( + fn try_from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() From a145e332dec73ebea1fa3576ae48be13e3ad104c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 17 Jul 2025 21:18:17 +0300 Subject: [PATCH 119/178] 1 file, 300 lines, many worries --- dsl/src/lib.rs | 437 ++++++++++++++++++++--------------------- input/src/input_dsl.rs | 45 ++--- 2 files changed, 239 insertions(+), 243 deletions(-) diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 416db82..437f4de 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -14,7 +14,110 @@ pub(crate) use konst::string::{split_at, str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; #[cfg(test)] mod test; -/// A DSL expression. Generic over string and expression storage. +/// Enumeration of values that may figure in an expression. +/// Generic over string and expression storage. +#[derive(Clone, Debug, PartialEq, Default)] +pub enum Val { + /// Empty expression + #[default] Nil, + /// Unsigned integer literal + Num(usize), + /// An identifier that starts with `.` + Sym(Str), + /// An identifier that doesn't start with `:` + Key(Str), + /// A quoted string literal + Str(Str), + /// A DSL expression. + Exp( + /// Number of unclosed parentheses. Must be 0 to be valid. + isize, + /// Expression content. + Exp + ), + /// An error. + Error(DslError), +} +impl Copy for Val {} +impl Val { + pub fn convert (&self) -> Val where + T::Str: for<'a> From<&'a Str>, + T::Exp: for<'a> From<&'a Exp> + { + match self { Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(*u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Error(e) => Val::Error(*e) } } +} +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {} +impl DslExp for T {} +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} +impl + std::ops::Deref> DslStr for T {} +impl> Val { + pub const fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} + pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} + pub const fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} + pub const fn sym (&self) -> Option<&Str> {match self{Val::Sym(s)=>Some(s), _=>None}} + pub const fn key (&self) -> Option<&Str> {match self{Val::Key(k)=>Some(k), _=>None}} + pub const fn str (&self) -> Option<&Str> {match self{Val::Str(s)=>Some(s), _=>None}} + pub const fn exp (&self) -> Option<&Exp> {match self{Val::Exp(_, x)=>Some(x),_=>None}} + pub const fn exp_depth (&self) -> Option {match self{Val::Exp(d, _)=>Some(*d), _=>None}} +} +/// Parsed substring with range and value. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Token { + /// Meaning of token. + pub value: Val, + /// Reference to source text. + pub source: Str, + /// Index of 1st character of span. + pub start: usize, + /// Length of span. + pub length: usize, +} +impl Copy for Token {} +impl Token { + pub const fn end (&self) -> usize { + self.start.saturating_add(self.length) } + pub const fn value (&self) + -> &Val { &self.value } + pub const fn err (&self) + -> Option { if let Val::Error(e) = self.value { Some(e) } else { None } } + pub const fn new (source: Str, start: usize, length: usize, value: Val) + -> Self { Self { value, start, length, source } } + pub const fn copy (&self) -> Self where Val: Copy, Str: Copy, Exp: Copy { + Self { value: self.value, ..*self } + } +} +impl<'s> CstToken<'s> { + pub const fn slice (&self) -> &str { + str_range(self.source, self.start, self.end()) } + pub const fn slice_exp (&self) -> &str { + str_range(self.source, self.start.saturating_add(1), self.end()) } + pub const fn grow (&mut self) -> &mut Self { + let max_length = self.source.len().saturating_sub(self.start); + self.length = self.length + 1; + if self.length > max_length { self.length = max_length } + self + } + pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { + self.value = Val::Exp(depth, Cst(CstConstIter(source))); + self + } +} +/// To the [Dsl], a token is equivalent to its `value` field. +impl Dsl for Token { + type Str = Str; type Exp = Exp; + fn dsl (&self) -> Val { self.value.clone() } +} +/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. pub trait Dsl: Clone + Debug { /// The string representation for a dizzle. type Str: DslStr; @@ -22,139 +125,48 @@ pub trait Dsl: Clone + Debug { type Exp: DslExp; /// Request the top-level DSL [Val]ue. /// May perform cloning or parsing. - fn dsl (&self) -> Val; - fn err (&self) -> Option {self.dsl().err()} - fn nil (&self) -> bool {self.dsl().nil()} - fn num (&self) -> Option {self.dsl().num()} - fn sym (&self) -> Option {self.dsl().sym()} - fn key (&self) -> Option {self.dsl().key()} - fn str (&self) -> Option {self.dsl().str()} - fn exp (&self) -> Option {self.dsl().exp()} + fn dsl (&self) -> Val; + fn err (&self) -> Option {self.dsl().err()} + fn nil (&self) -> bool {self.dsl().nil()} + fn num (&self) -> Option {self.dsl().num()} + fn sym (&self) -> Option {self.dsl().sym()} + fn key (&self) -> Option {self.dsl().key()} + fn str (&self) -> Option {self.dsl().str()} + fn exp (&self) -> Option {self.dsl().exp()} + fn exp_depth (&self) -> Option {self.dsl().exp_depth()} + fn exp_head (&self) -> Val {self.dsl().exp_head()} + fn exp_tail (&self) -> Val {self.dsl().exp_tail()} } -/// Enumeration of values that may figure in an expression. -/// Generic over [Dsl] implementation. -#[derive(Clone, Debug, PartialEq, Default)] -pub enum Val { - /// Empty expression - #[default] Nil, - /// Unsigned integer literal - Num(usize), - /// An identifier that starts with `.` - Sym(D::Str), - /// An identifier that doesn't start with `:` - Key(D::Str), - /// A quoted string literal - Str(D::Str), - /// A sub-expression. - Exp( - /// Expression depth checksum. Must be 0, otherwise you have an unclosed delimiter. - isize, - /// Expression content. - D::Exp - ), - /// An error. - Error(DslError), -} -impl Val { - pub fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} - pub fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} - pub fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} - pub fn sym (&self) -> Option {match self{Val::Sym(s)=>Some(s.clone()), _=>None}} - pub fn key (&self) -> Option {match self{Val::Key(k)=>Some(k.clone()), _=>None}} - pub fn str (&self) -> Option {match self{Val::Str(s)=>Some(s.clone()), _=>None}} - pub fn exp (&self) -> Option {match self{Val::Exp(_, x)=>Some(x.clone()),_=>None}} +/// The most basic implementor of the [Dsl] trait. +impl Dsl for Val { + type Str = Str; type Exp = Exp; + fn dsl (&self) -> Val { self.clone() } } /// The abstract syntax tree (AST) can be produced from the CST /// by cloning source slices into owned ([Arc]) string slices. #[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(Arc>>>); +pub struct Ast(Arc, Ast>>>>); impl Dsl for Ast { - type Str = Arc; - type Exp = Arc>>>; - fn dsl (&self) -> Val { Val::Exp(0, self.0.clone()) } + type Str = Arc; type Exp = Ast; + fn dsl (&self) -> Val, Ast> { Val::Exp(0, Ast(self.0.clone())) } } /// The concrete syntax tree (CST) implements zero-copy /// parsing of the DSL from a string reference. CST items /// preserve info about their location in the source. /// CST stores strings as source references and expressions as [CstIter] instances. #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Cst<'s>(CstConstIter<'s>); +pub struct Cst<'s>(pub CstConstIter<'s>); +pub type CstVal<'s> = Val<&'s str, Cst<'s>>; +pub type CstToken<'s> = Token<&'s str, Cst<'s>>; impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = CstConstIter<'s>; - fn dsl (&self) -> Val { Val::Exp(0, self.0) } + type Str = &'s str; type Exp = Cst<'s>; + fn dsl (&self) -> Val { Val::Exp(0, Cst(self.0)) } } impl<'s> From<&'s str> for Cst<'s> { fn from (source: &'s str) -> Self { Self(CstConstIter(source)) } } -/// The string representation for a [Dsl] implementation. -/// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} -impl + std::ops::Deref> DslStr for T {} -/// The expression representation for a [Dsl] implementation. -/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. -pub trait DslExp: PartialEq + Clone + Default + Debug { - fn head (&self) -> Val; - fn tail (&self) -> Self; -} -impl DslExp for Arc>>> { - fn head (&self) -> Val { - self.get(0).cloned().unwrap_or_else(||Arc::new(Default::default())).value.into() - } - fn tail (&self) -> Self { - Self::new(self.iter().skip(1).cloned().collect()) - } -} -impl<'s> DslExp for CstConstIter<'s> { - fn head (&self) -> Val { - peek(self.0).value.into() - } - fn tail (&self) -> Self { - let Token { span: Span { start, length, source }, .. } = peek(self.0); - Self(&source[(start+length)..]) - } -} -impl + Copy> Copy for Val {} -impl Val { - pub fn is_nil (&self) -> bool { matches!(self, Self::Nil) } - pub fn as_error (&self) -> Option<&DslError> { if let Self::Error(e) = self { Some(e) } else { None } } - pub fn as_num (&self) -> Option {match self{Self::Num(n)=>Some(*n),_=>None}} - pub fn as_sym (&self) -> Option<&str> {match self{Self::Sym(s )=>Some(s.as_ref()),_=>None}} - pub fn as_key (&self) -> Option<&str> {match self{Self::Key(k )=>Some(k.as_ref()),_=>None}} - pub fn as_str (&self) -> Option<&str> {match self{Self::Str(s )=>Some(s.as_ref()),_=>None}} - //pub fn as_exp (&self) -> Option<&D::Exp> {match self{Self::Exp(_, x)=>Some(x),_=>None}} - pub fn exp_depth (&self) -> Option { todo!() } - pub fn exp_head_tail (&self) -> (Option<&Self>, Option<&D::Exp>) { (self.exp_head(), self.exp_tail()) } - pub fn exp_head (&self) -> Option<&Self> { todo!() } // TODO - pub fn exp_tail (&self) -> Option<&D::Exp> { todo!() } - pub fn peek (&self) -> Option { todo!() } - pub fn next (&mut self) -> Option { todo!() } - pub fn rest (self) -> Vec { todo!() } - //pub fn convert (&self) -> Val where - //T::Str: for<'a> From<&'a D::Str>, - //T::Exp: for<'a> From<&'a D::Exp> - //{ - //match self { Val::Nil => Val::Nil, - //Val::Num(u) => Val::Num(*u), - //Val::Sym(s) => Val::Sym(s.into()), - //Val::Key(s) => Val::Key(s.into()), - //Val::Str(s) => Val::Str(s.into()), - //Val::Exp(d, x) => Val::Exp(*d, x.into()), - //Val::Error(e) => Val::Error(*e) } } - //pub fn exp_match (&self, namespace: &str, cb: F) -> DslPerhaps - //where F: Fn(&str, &Exp)-> DslPerhaps { - //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) - //} - //} -} - /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { #[error("parse failed: not implemented")] @@ -175,15 +187,15 @@ impl Val { /// [Cst::next] returns just the [Cst] and mutates `self`, /// instead of returning an updated version of the struct as [CstConstIter::next] does. #[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'s>(CstConstIter<'s>); +pub struct CstIter<'s>(pub CstConstIter<'s>); impl<'s> CstIter<'s> { pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } } impl<'s> Iterator for CstIter<'s> { - type Item = Token>; + type Item = CstToken<'s>; fn next (&mut self) -> Option { match self.0.advance() { - Some((item, rest)) => { self.0 = rest; item.into() }, + Some((item, rest)) => { self.0 = rest; Some(item.into()) }, None => None, } } @@ -199,8 +211,8 @@ impl<'s> From <&'s str> for CstConstIter<'s> { fn from (src: &'s str) -> Self { Self(src) } } impl<'s> Iterator for CstConstIter<'s> { - type Item = Token>; - fn next (&mut self) -> Option>> { self.advance().map(|x|x.0) } + type Item = CstToken<'s>; + fn next (&mut self) -> Option> { self.advance().map(|x|x.0) } } impl<'s> ConstIntoIter for CstConstIter<'s> { type Kind = IsIteratorKind; @@ -210,17 +222,100 @@ impl<'s> ConstIntoIter for CstConstIter<'s> { impl<'s> CstConstIter<'s> { pub const fn new (source: &'s str) -> Self { Self(source) } pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn advance (&mut self) -> Option<(Token>, Self)> { - match peek(self.0) { + pub const fn advance (&mut self) -> Option<(CstToken<'s>, Self)> { + match peek(Val::Nil, self.0) { Token { value: Val::Nil, .. } => None, token => { - let end = self.chomp(token.span.end()); + let end = self.chomp(token.end()); Some((token.copy(), end)) }, } } } +pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> { + use Val::*; + let mut start = 0; + let mut length = 0; + let mut source = source; + loop { + if let Some(((i, c), next)) = char_indices(source).next() { + if matches!(value, Error(_)) { + break + } else if matches!(value, Nil) { + if is_whitespace(c) { + length += 1; + continue + } + start = i; + length = 1; + if is_exp_start(c) { + value = Exp(1, Cst(CstConstIter(str_range(source, i, i+1)))); + } else if is_str_start(c) { + value = Str(str_range(source, i, i+1)); + } else if is_sym_start(c) { + value = Sym(str_range(source, i, i+1)); + } else if is_key_start(c) { + value = Key(str_range(source, i, i+1)); + } else if is_digit(c) { + value = match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }; + } else { + value = Error(Unexpected(c)); + break + } + } else if matches!(value, Str(_)) { + if is_str_end(c) { + break + } else { + value = Str(str_range(source, start, start + length + 1)); + } + } else if matches!(value, Sym(_)) { + if is_sym_end(c) { + break + } else if is_sym_char(c) { + value = Sym(str_range(source, start, start + length + 1)); + } else { + value = Error(Unexpected(c)); + } + } else if matches!(value, Key(_)) { + if is_key_end(c) { + break + } + length += 1; + if is_key_char(c) { + value = Key(str_range(source, start, start + length + 1)); + } else { + value = Error(Unexpected(c)); + } + } else if let Exp(depth, exp) = value { + if depth == 0 { + value = Exp(0, Cst(CstConstIter(str_range(source, start, start + length)))); + break + } + length += 1; + if c == ')' { + value = Exp(depth-1, Cst(CstConstIter(str_range(source, start, start + length)))); + } else if c == '(' { + value = Exp(depth+1, Cst(CstConstIter(str_range(source, start, start + length)))); + } else { + value = Exp(depth, Cst(CstConstIter(str_range(source, start, start + length)))); + } + } else if let Num(m) = value { + if is_num_end(c) { + break + } + length += 1; + match to_digit(c) { + Ok(n) => { value = Num(n+10*m); }, + Err(e) => { value = Error(e); } + } + } + } else { + break + } + } + return Token { value, source, start, length } +} const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } @@ -233,48 +328,6 @@ const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } const fn is_str_start (c: char) -> bool { matches!(c, '"') } const fn is_str_end (c: char) -> bool { matches!(c, '"') } const fn is_exp_start (c: char) -> bool { matches!(c, '(') } - -pub const fn peek <'s> (src: &'s str) -> Token> { - use Val::*; - let mut t = Token { value: Val::Nil, span: Span { source: src, start: 0, length: 0 } }; - let mut iter = char_indices(src); - while let Some(((i, c), next)) = iter.next() { - t = match (t.value(), c) { - (Error(_), _) => return t, - - (Nil, _) if is_exp_start(c) => Token::new(src, i, 1, Exp(1, CstConstIter(str_range(src, i, i+1)))), - (Nil, _) if is_str_start(c) => Token::new(src, i, 1, Str(str_range(src, i, i+1))), - (Nil, _) if is_sym_start(c) => Token::new(src, i, 1, Sym(str_range(src, i, i+1))), - (Nil, _) if is_key_start(c) => Token::new(src, i, 1, Key(str_range(src, i, i+1))), - (Nil, _) if is_digit(c) => Token::new(src, i, 1, match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }), - (Nil, _) if is_whitespace(c) => t.grown(), - (Nil, _) => { t.value = Val::Error(Unexpected(c)); t }, - - (Str(_), _) if is_str_end(c) => return t, - (Str(_), _) => { t.value = Str(t.span.grow().slice()); t }, - - (Sym(_), _) if is_sym_end(c) => return t, - (Sym(_), _) if is_sym_char(c) => { t.value = Sym(t.span.grow().slice()); t }, - (Sym(_), _) => { t.value = Error(Unexpected(c)); t }, - - (Key(_), _) if is_key_end(c) => return t, - (Key(_), _) if is_key_char(c) => { t.value = Key(t.span.grow().slice()); t }, - (Key(_), _) => { t.value = Error(Unexpected(c)); t }, - - (Exp(0, _), _) => { t.value = Exp(0, CstConstIter(t.span.grow().slice_exp())); return t }, - (Exp(d, _), ')') => { t.value = Exp((*d)-1, CstConstIter(t.span.grow().slice_exp())); t }, - (Exp(d, _), '(') => { t.value = Exp((*d)+1, CstConstIter(t.span.grow().slice_exp())); t }, - (Exp(d, _), _ ) => { t.value = Exp(*d, CstConstIter(t.span.grow().slice_exp())); t }, - - (Num(m), _) if is_num_end(c) => return t, - (Num(m), _) => match to_digit(c) { - Ok(n) => { let m = *m; t.span.grow(); t.value = Num(n+10*m); t }, - Err(e) => { t.span.grow(); t.value = Error(e); t } }, - }; - iter = next; - } - t -} pub const fn to_number (digits: &str) -> Result { let mut iter = char_indices(digits); let mut value = 0; @@ -294,60 +347,6 @@ pub const fn to_digit (c: char) -> Result { _ => return Err(Unexpected(c)) }) } -/// Parsed substring with range and value. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Token { - /// Source span of token. - span: Span, - /// Meaning of token. - value: Val, -} -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Span { - /// Reference to source text. - pub source: D::Str, - /// Index of 1st character of span. - pub start: usize, - /// Length of span. - pub length: usize, -} -impl<'s, D: Dsl> Span { - pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } -} -impl<'s, D: Dsl> Span { - pub const fn slice (&self) -> &'s str { - str_range(self.source, self.start, self.end()) } - pub const fn slice_exp (&self) -> &'s str { - str_range(self.source, self.start.saturating_add(1), self.end()) } - pub const fn grow (&mut self) -> &mut Self { - let max_length = self.source.len().saturating_sub(self.start); - self.length = self.length + 1; - if self.length > max_length { self.length = max_length } - self } -} - -impl Token { - pub const fn value (&self) -> &Val { &self.value } - pub const fn span (&self) -> &Span { &self.span } - pub const fn err (&self) -> Option { - if let Val::Error(e) = self.value { Some(e) } else { None } } - pub const fn new (source: D::Str, start: usize, length: usize, value: Val) -> Self { - Self { value, span: Span { source, start, length } } } - pub const fn copy (&self) -> Self where D::Str: Copy, D::Exp: Copy, Val: Copy { - Self { span: Span { ..self.span }, value: self.value } } -} -impl<'s, D: Dsl>> Token { - pub const fn grown (mut self) -> Self { self.span.grow(); self } - pub const fn grow_exp (&mut self, d: isize) -> &mut Self where D::Exp: From<&'s str> { - if let Val::Exp(depth, _) = self.value() { - self.value = Val::Exp(*depth as isize + d, CstConstIter(self.span.slice_exp())); - self - } else { - unreachable!() - } - } -} - /// `State` + [Dsl] -> `Self`. pub trait FromDsl: Sized { fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 882612d..49fb270 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -30,37 +30,34 @@ impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { Self::from_source(read_and_leak(path)?) } /// Create input layer collection from string. - pub fn from_source (source: impl AsRef) -> Usually { - Self::from_dsl(D::from(Cst::from(source.as_ref()))) + pub fn from_source (source: &'s str) -> Usually { + Self::from_dsl(D::from(Cst(CstConstIter(source)))) } /// Create input layer collection from DSL. pub fn from_dsl (dsl: D) -> Usually { use Val::*; let mut input_map: BTreeMap>> = Default::default(); - match dsl.exp() { - Some(exp) => match exp.head() { - Some(Str(path)) => { - let path = PathBuf::from(path.as_ref()); - for (key, val) in InputMap::::from_path(&path)?.0.into_iter() { - todo!("import {path:?} {key:?} {val:?}"); - if !input_map.contains_key(&key) { - input_map.insert(key, vec![]); - } + match dsl.exp_head() { + Str(path) => { + let path = PathBuf::from(path.as_ref()); + for (key, val) in InputMap::::from_path(&path)?.0.into_iter() { + todo!("import {path:?} {key:?} {val:?}"); + if !input_map.contains_key(&key) { + input_map.insert(key, vec![]); } - }, - Some(Sym(sym)) => { - //let key: I = sym.into(); - //if !input_map.contains_key(&key) { - //input_map.insert(key, vec![]); - //} - todo!("binding {sym:?} {:?}", exp.tail()); - }, - Some(Key("if")) => { - todo!("conditional binding {:?}", exp.tail()); - }, - _ => return Err(format!("invalid form in keymap: {exp:?}").into()) + } }, - _ => return Err(format!("not an expression: {dsl:?}").into()) + Sym(sym) => { + //let key: I = sym.into(); + //if !input_map.contains_key(&key) { + //input_map.insert(key, vec![]); + //} + todo!("binding {sym:?} {:?}", dsl.exp_tail()); + }, + Key(s) if s.as_ref() == "if" => { + todo!("conditional binding {:?}", dsl.exp_tail()); + }, + _ => return Err(format!("invalid form in keymap: {dsl:?}").into()) } Ok(Self(input_map)) } From 291b917970cd1d764258eeb22fbd1884dfac7cc1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 19 Jul 2025 07:55:56 +0300 Subject: [PATCH 120/178] this one at least compiles --- Cargo.lock | 381 ++++++++++++++++++++++++++++----------- Cargo.toml | 2 +- dsl/src/lib.rs | 115 +++++++----- input/src/input_dsl.rs | 98 +++++----- proc/src/proc_command.rs | 4 +- proc/src/proc_expose.rs | 10 +- proc/src/proc_view.rs | 4 +- 7 files changed, 416 insertions(+), 198 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c5c30d..08f6979 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "allocator-api2" @@ -40,9 +40,9 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -56,7 +56,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -86,15 +86,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "by_address" @@ -110,18 +110,18 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "compact_str" @@ -154,6 +154,31 @@ name = "const_panic" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e" +dependencies = [ + "const_panic_proc_macros", + "typewit", +] + +[[package]] +name = "const_panic_proc_macros" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c5b80a80fb52c1a6ca02e3cd829a76b472ff0a15588196fd8da95221f0c1e4b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "crossbeam-utils" @@ -177,6 +202,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.0.8", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -221,6 +264,36 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.15.0" @@ -241,12 +314,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -281,7 +354,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -304,9 +377,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -333,9 +406,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "instability" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ "darling", "indoc", @@ -413,9 +486,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" @@ -430,10 +503,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] -name = "lock_api" -version = "0.4.12" +name = "litrs" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -456,29 +535,29 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -515,7 +594,7 @@ dependencies = [ "fast-srgb8", "palette_derive", "phf", - "rand", + "rand 0.8.5", ] [[package]] @@ -532,9 +611,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -542,15 +621,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -576,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -621,17 +700,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", "bitflags", "lazy_static", "num-traits", - "rand", - "rand_chacha", + "rand 0.9.1", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -652,15 +731,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -682,9 +761,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -693,8 +772,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -704,7 +793,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -717,12 +816,21 @@ dependencies = [ ] [[package]] -name = "rand_xorshift" -version = "0.3.0" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "rand_core", + "getrandom 0.3.3", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", ] [[package]] @@ -734,7 +842,7 @@ dependencies = [ "bitflags", "cassowary", "compact_str", - "crossterm", + "crossterm 0.28.1", "indoc", "instability", "itertools 0.13.0", @@ -757,9 +865,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -772,9 +880,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustix" @@ -791,22 +899,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -870,9 +978,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "static_assertions" @@ -910,9 +1018,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -921,14 +1029,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -936,7 +1044,7 @@ dependencies = [ name = "tengri" version = "0.13.0" dependencies = [ - "crossterm", + "crossterm 0.29.0", "tengri", "tengri_core", "tengri_dsl", @@ -954,6 +1062,7 @@ version = "0.13.0" name = "tengri_dsl" version = "0.13.0" dependencies = [ + "const_panic", "itertools 0.14.0", "konst", "proptest", @@ -1000,11 +1109,11 @@ version = "0.13.0" dependencies = [ "atomic_float", "better-panic", - "crossterm", + "crossterm 0.29.0", "konst", "palette", "quanta", - "rand", + "rand 0.8.5", "ratatui", "tengri", "tengri_core", @@ -1091,6 +1200,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -1102,9 +1217,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1204,22 +1319,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] @@ -1228,14 +1343,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1244,48 +1375,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -1297,18 +1476,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index fa89dab..a204087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ edition = "2024" atomic_float = { version = "1" } better-panic = { version = "0.3.0" } const_panic = { version = "0.2.12", features = [ "derive" ] } -crossterm = { version = "0.28.1" } +crossterm = { version = "0.29.0" } heck = { version = "0.5" } itertools = { version = "0.14.0" } konst = { version = "0.3.16", features = [ "rust_1_83" ] } diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs index 437f4de..703f8ba 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/lib.rs @@ -44,13 +44,16 @@ impl Val { T::Str: for<'a> From<&'a Str>, T::Exp: for<'a> From<&'a Exp> { - match self { Val::Nil => Val::Nil, - Val::Num(u) => Val::Num(*u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(*d, x.into()), - Val::Error(e) => Val::Error(*e) } } + match self { + Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(*u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Error(e) => Val::Error(*e) + } + } } /// The expression representation for a [Dsl] implementation. /// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. @@ -96,22 +99,6 @@ impl Token { Self { value: self.value, ..*self } } } -impl<'s> CstToken<'s> { - pub const fn slice (&self) -> &str { - str_range(self.source, self.start, self.end()) } - pub const fn slice_exp (&self) -> &str { - str_range(self.source, self.start.saturating_add(1), self.end()) } - pub const fn grow (&mut self) -> &mut Self { - let max_length = self.source.len().saturating_sub(self.start); - self.length = self.length + 1; - if self.length > max_length { self.length = max_length } - self - } - pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { - self.value = Val::Exp(depth, Cst(CstConstIter(source))); - self - } -} /// To the [Dsl], a token is equivalent to its `value` field. impl Dsl for Token { type Str = Str; type Exp = Exp; @@ -126,16 +113,17 @@ pub trait Dsl: Clone + Debug { /// Request the top-level DSL [Val]ue. /// May perform cloning or parsing. fn dsl (&self) -> Val; - fn err (&self) -> Option {self.dsl().err()} - fn nil (&self) -> bool {self.dsl().nil()} - fn num (&self) -> Option {self.dsl().num()} - fn sym (&self) -> Option {self.dsl().sym()} - fn key (&self) -> Option {self.dsl().key()} - fn str (&self) -> Option {self.dsl().str()} - fn exp (&self) -> Option {self.dsl().exp()} + fn err (&self) -> Option {self.dsl().err()} + fn nil (&self) -> bool {self.dsl().nil()} + fn num (&self) -> Option {self.dsl().num()} + fn sym (&self) -> Option {self.dsl().sym()} + fn key (&self) -> Option {self.dsl().key()} + fn str (&self) -> Option {self.dsl().str()} + fn exp (&self) -> Option {self.dsl().exp()} fn exp_depth (&self) -> Option {self.dsl().exp_depth()} fn exp_head (&self) -> Val {self.dsl().exp_head()} - fn exp_tail (&self) -> Val {self.dsl().exp_tail()} + fn exp_tail (&self) -> Self::Exp {self.dsl().exp_tail()} + fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() } } /// The most basic implementor of the [Dsl] trait. impl Dsl for Val { @@ -146,25 +134,71 @@ impl Dsl for Val { /// by cloning source slices into owned ([Arc]) string slices. #[derive(Debug, Clone, Default, PartialEq)] pub struct Ast(Arc, Ast>>>>); +pub type AstVal = Val, Ast>; +pub type AstToken = Token, Ast>; impl Dsl for Ast { type Str = Arc; type Exp = Ast; fn dsl (&self) -> Val, Ast> { Val::Exp(0, Ast(self.0.clone())) } } +impl<'s> From<&'s str> for Ast { + fn from (source: &'s str) -> Self { + let source: Arc = source.into(); + Self(CstIter(CstConstIter(source.as_ref())) + .map(|token|Arc::new(Token { + source: source.clone(), + start: token.start, + length: token.length, + value: match token.value { + Val::Nil => Val::Nil, + Val::Num(u) => Val::Num(u), + Val::Sym(s) => Val::Sym(s.into()), + Val::Key(s) => Val::Key(s.into()), + Val::Str(s) => Val::Str(s.into()), + Val::Exp(d, x) => Val::Exp(d, x.into()), + Val::Error(e) => Val::Error(e.into()) + }, + })) + .collect::>() + .into()) + } +} +impl<'s> From> for Ast { + fn from (cst: Cst<'s>) -> Self { + let mut tokens: VecDeque<_> = Default::default(); + Self(tokens.into()) + } +} /// The concrete syntax tree (CST) implements zero-copy /// parsing of the DSL from a string reference. CST items /// preserve info about their location in the source. /// CST stores strings as source references and expressions as [CstIter] instances. #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstConstIter<'s>); +pub struct Cst<'s>(pub CstIter<'s>); pub type CstVal<'s> = Val<&'s str, Cst<'s>>; pub type CstToken<'s> = Token<&'s str, Cst<'s>>; +impl<'s> CstToken<'s> { + pub const fn slice (&self) -> &str { + str_range(self.source, self.start, self.end()) } + pub const fn slice_exp (&self) -> &str { + str_range(self.source, self.start.saturating_add(1), self.end()) } + pub const fn grow (&mut self) -> &mut Self { + let max_length = self.source.len().saturating_sub(self.start); + self.length = self.length + 1; + if self.length > max_length { self.length = max_length } + self + } + pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { + self.value = Val::Exp(depth, Cst(CstIter(CstConstIter(source)))); + self + } +} impl<'s> Dsl for Cst<'s> { type Str = &'s str; type Exp = Cst<'s>; fn dsl (&self) -> Val { Val::Exp(0, Cst(self.0)) } } impl<'s> From<&'s str> for Cst<'s> { fn from (source: &'s str) -> Self { - Self(CstConstIter(source)) + Self(CstIter(CstConstIter(source))) } } /// DSL-specific error codes. @@ -250,7 +284,7 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> start = i; length = 1; if is_exp_start(c) { - value = Exp(1, Cst(CstConstIter(str_range(source, i, i+1)))); + value = Exp(1, Cst(CstIter(CstConstIter(str_range(source, i, i+1))))); } else if is_str_start(c) { value = Str(str_range(source, i, i+1)); } else if is_sym_start(c) { @@ -289,17 +323,14 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } } else if let Exp(depth, exp) = value { if depth == 0 { - value = Exp(0, Cst(CstConstIter(str_range(source, start, start + length)))); + value = Exp(0, Cst(CstIter(CstConstIter(str_range(source, start, start + length))))); break } length += 1; - if c == ')' { - value = Exp(depth-1, Cst(CstConstIter(str_range(source, start, start + length)))); - } else if c == '(' { - value = Exp(depth+1, Cst(CstConstIter(str_range(source, start, start + length)))); - } else { - value = Exp(depth, Cst(CstConstIter(str_range(source, start, start + length)))); - } + value = Exp( + if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth }, + Cst(CstIter(CstConstIter(str_range(source, start, start + length)))) + ); } else if let Num(m) = value { if is_num_end(c) { break diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 49fb270..5032319 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -4,24 +4,64 @@ 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 cond, /// so that only certain layers are active at a given time depending on state. -#[derive(Debug)] pub struct InputMap( - /// Map of input event (key combination) to +/// +/// When a key is pressed, the bindings for it are checked in sequence. +/// When the first non-conditional or true conditional binding is executed, +/// that binding's value is returned. +#[derive(Debug)] pub struct EventMap( + /// Map of each event (e.g. key combination) to /// all command expressions bound to it by /// all loaded input layers. - pub BTreeMap>> + pub EventBindings ); -impl Default for InputMap { - fn default () -> Self { - Self(Default::default()) - } -} -#[derive(Debug, Default)] pub struct InputBinding { +type EventBindings = BTreeMap>>; +/// An input binding. +#[derive(Debug, Default)] pub struct Binding { condition: Option, command: D, description: Option>, source: Option>, } -impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { +impl Default for EventMap { + fn default () -> Self { + Self(Default::default()) + } +} +impl> + Clone> EventMap { + /// Create input layer collection from DSL. + pub fn new (dsl: Ast) -> Usually { + use Val::*; + let mut input_map: EventBindings = Default::default(); + match dsl.exp_head() { + Str(path) => { + let path = PathBuf::from(path.as_ref() as &str); + for (key, val) in Self::from_path(&path)?.0.into_iter() { + todo!("import {path:?} {key:?} {val:?}"); + let key: E = key.into(); + if !input_map.contains_key(&key) { + input_map.insert(key, vec![]); + } + } + }, + Sym(s) => { + let key: Arc = s.as_ref().into(); + let key: E = key.into(); + if !input_map.contains_key(&key) { + input_map.insert(key.clone(), vec![]); + } + todo!("binding {s:?} {:?}", dsl.exp_tail()); + }, + Key(k) if k.as_ref() == "if" => { + todo!("conditional binding {:?}", dsl.exp_tail()); + }, + _ => return Err(format!("invalid form in keymap: {dsl:?}").into()) + } + Ok(Self(input_map)) + } + /// Create input layer collection from string. + pub fn from_source (source: &str) -> Usually { + Self::new(Ast::from(source)) + } /// Create input layer collection from path to text file. pub fn from_path > (path: P) -> Usually { if !exists(path.as_ref())? { @@ -29,41 +69,9 @@ impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { } Self::from_source(read_and_leak(path)?) } - /// Create input layer collection from string. - pub fn from_source (source: &'s str) -> Usually { - Self::from_dsl(D::from(Cst(CstConstIter(source)))) - } - /// Create input layer collection from DSL. - pub fn from_dsl (dsl: D) -> Usually { - use Val::*; - let mut input_map: BTreeMap>> = Default::default(); - match dsl.exp_head() { - Str(path) => { - let path = PathBuf::from(path.as_ref()); - for (key, val) in InputMap::::from_path(&path)?.0.into_iter() { - todo!("import {path:?} {key:?} {val:?}"); - if !input_map.contains_key(&key) { - input_map.insert(key, vec![]); - } - } - }, - Sym(sym) => { - //let key: I = sym.into(); - //if !input_map.contains_key(&key) { - //input_map.insert(key, vec![]); - //} - todo!("binding {sym:?} {:?}", dsl.exp_tail()); - }, - Key(s) if s.as_ref() == "if" => { - todo!("conditional binding {:?}", dsl.exp_tail()); - }, - _ => return Err(format!("invalid form in keymap: {dsl:?}").into()) - } - Ok(Self(input_map)) - } /// 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 + pub fn handle (&self, state: &mut S, event: Ast) -> Perhaps where S: DslInto + DslInto, O: Command { @@ -76,7 +84,7 @@ impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { //} //if matches //&& let Some(exp) = bind.val().exp_head() - //&& input.dsl_into(exp, ||format!("InputMap: input.eval(binding) failed").into())? + //&& input.dsl_into(exp, ||format!("EventMap: input.eval(binding) failed").into())? //&& let Some(command) = state.try_dsl_into(exp)? { //return Ok(Some(command)) //} @@ -112,7 +120,7 @@ impl<'s, I: Debug + Ord, D: Dsl + From>> InputMap { //let mut keys = iter.unwrap(); - //let mut map = InputMap::default(); + //let mut map = EventMap::default(); //while let Some(token) = keys.next() { //if let Value::Exp(_, mut exp) = token.value { //let next = exp.next(); diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 132d867..36b25d5 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -93,7 +93,7 @@ impl ToTokens for CommandDef { out }; write_quote(quote! { - Some(::tengri::dsl::Token { value: ::tengri::dsl::DslVal::Key(#key), .. }) => { + Some(::tengri::dsl::Token { value: ::tengri::dsl::Val::Key(#key), .. }) => { let mut words = words.clone(); Some(#command_enum::#variant) }, @@ -154,7 +154,7 @@ impl ToTokens for CommandDef { state: &#state, value: &impl ::tengri::dsl::Dsl ) -> Perhaps { - use ::tengri::dsl::DslVal::*; + use ::tengri::dsl::Val::*; todo!()//Ok(match token { #(#matchers)* _ => None }) } } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index ca5c9e7..3514696 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -81,7 +81,7 @@ impl ExposeImpl { /// Generated by [tengri_proc::expose]. impl ::tengri::dsl::FromDsl<#state> for #t { fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { - Ok(Some(match dsl.val() { + Ok(Some(match dsl.dsl() { #arms _ => { return Ok(None) } })) @@ -93,7 +93,7 @@ impl ExposeImpl { let formatted_type = format!("{}", quote! { #t }); if &formatted_type == "bool" { return quote! { - ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + ::tengri::dsl::Val::Sym(s) => match s.as_ref() { ":true" => true, ":false" => false, #variants @@ -110,16 +110,16 @@ impl ExposeImpl { Span::call_site() ); return quote! { - ::tengri::dsl::DslVal::Num(n) => TryInto::<#t>::try_into(n) + ::tengri::dsl::Val::Num(n) => TryInto::<#t>::try_into(n) .unwrap_or_else(|_|panic!(#num_err)), - ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + ::tengri::dsl::Val::Sym(s) => match s.as_ref() { #variants _ => { return Ok(None) } }, } } return quote! { - ::tengri::dsl::DslVal::Sym(s) => match s.as_ref() { + ::tengri::dsl::Val::Sym(s) => match s.as_ref() { #variants _ => { return Ok(None) } }, diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 0f377de..5e11c0f 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -76,7 +76,7 @@ impl ViewDef { fn builtins (&self) -> impl ToTokens { let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { - ::tengri::dsl::DslVal::Exp(_, expr) => return Ok(Some( + ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? .boxed() )), @@ -89,7 +89,7 @@ impl ViewDef { let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { #key => return Ok(Some(self.#value().boxed())), })); - quote! { ::tengri::dsl::DslVal::Sym(key) => match key.as_ref() { #(#exposed)* } } + quote! { ::tengri::dsl::Val::Sym(key) => match key.as_ref() { #(#exposed)* } } } } From 238ac2e8882b584371d916cc5cba76a2fc6878d1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 19 Jul 2025 08:42:08 +0300 Subject: [PATCH 121/178] simplify --- dsl/Cargo.toml | 3 + dsl/src/{lib.rs => dsl.rs} | 374 +++++++++++++++++++----------- dsl/src/dsl_test.rs | 0 dsl/src/test.rs | 104 --------- input/src/input_dsl.rs | 4 +- output/src/lib.rs | 3 + output/src/ops.rs | 387 ++++++++++++++++++++++++++++++-- output/src/ops/_collect.rs | 23 -- output/src/ops/_reduce.rs | 93 -------- output/src/ops/map.rs | 150 ------------- output/src/ops/memo.rs | 30 --- output/src/ops/stack.rs | 109 --------- output/src/ops/thunk.rs | 69 ------ output/src/space.rs | 279 ++++++++++++++++++++++- output/src/space/area.rs | 98 -------- output/src/space/coordinate.rs | 31 --- output/src/space/direction.rs | 22 -- output/src/space/measure.rs | 91 -------- output/src/space/size.rs | 40 ---- proc/src/proc_command.rs | 7 +- proc/src/proc_expose.rs | 4 +- proc/src/proc_view.rs | 8 +- tui/src/tui_engine.rs | 4 +- tui/src/tui_engine/tui_input.rs | 140 ++++++++++-- tui/src/tui_engine/tui_keys.rs | 92 -------- 25 files changed, 1018 insertions(+), 1147 deletions(-) rename dsl/src/{lib.rs => dsl.rs} (60%) create mode 100644 dsl/src/dsl_test.rs delete mode 100644 dsl/src/test.rs delete mode 100644 output/src/ops/_collect.rs delete mode 100644 output/src/ops/_reduce.rs delete mode 100644 output/src/ops/map.rs delete mode 100644 output/src/ops/memo.rs delete mode 100644 output/src/ops/stack.rs delete mode 100644 output/src/ops/thunk.rs delete mode 100644 output/src/space/area.rs delete mode 100644 output/src/space/coordinate.rs delete mode 100644 output/src/space/direction.rs delete mode 100644 output/src/space/measure.rs delete mode 100644 output/src/space/size.rs delete mode 100644 tui/src/tui_engine/tui_keys.rs diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml index 256b599..c61a057 100644 --- a/dsl/Cargo.toml +++ b/dsl/Cargo.toml @@ -4,6 +4,9 @@ description = "UI metaframework, tiny S-expression-based DSL." version = { workspace = true } edition = { workspace = true } +[lib] +path = "src/dsl.rs" + [dependencies] tengri_core = { path = "../core" } konst = { workspace = true } diff --git a/dsl/src/lib.rs b/dsl/src/dsl.rs similarity index 60% rename from dsl/src/lib.rs rename to dsl/src/dsl.rs index 703f8ba..2f1c150 100644 --- a/dsl/src/lib.rs +++ b/dsl/src/dsl.rs @@ -5,6 +5,7 @@ extern crate const_panic; use const_panic::{concat_panic, PanicFmt}; pub(crate) use ::tengri_core::*; +use std::ops::Deref; pub(crate) use std::error::Error; pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; @@ -13,7 +14,80 @@ 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::*; -#[cfg(test)] mod test; +#[cfg(test)] mod dsl_test; + +#[macro_export] macro_rules! dsl_read_advance (($exp:ident, $pat:pat => $val:expr)=>{{ + let (head, tail) = $exp.advance(); $exp = tail; match head { + Some($pat) => $val, _ => Err(format!("(e4) unexpected {head:?}").into()) }}}); + +/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. +pub trait Dsl: Clone + Debug { + /// The string representation for a dizzle. + type Str: DslStr; + /// The string representation for a dizzle. + type Exp: DslExp; + /// Request the top-level DSL [Val]ue. + /// May perform cloning or parsing. + fn val (&self) -> Val; + fn err (&self) -> Option {self.val().err()} + fn nil (&self) -> bool {self.val().nil()} + fn num (&self) -> Option {self.val().num()} + fn sym (&self) -> Option {self.val().sym()} + fn key (&self) -> Option {self.val().key()} + fn str (&self) -> Option {self.val().str()} + fn exp (&self) -> Option {self.val().exp()} + fn exp_depth (&self) -> Option {self.val().exp_depth()} + fn exp_head (&self) -> Val {self.val().exp_head()} + fn exp_tail (&self) -> Self::Exp {self.val().exp_tail()} + fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { + todo!() + } + fn advance (&self) -> (Val, Self::Exp) { + (self.exp_head(), self.exp_tail()) + } +} + +impl<'s> Dsl for &'s str { + type Str = &'s str; + type Exp = &'s str; + fn val (&self) -> Val { Val::Exp(0, self) } +} + +impl<'s> Dsl for Cst<'s> { + type Str = &'s str; + type Exp = Cst<'s>; + fn val (&self) -> Val { Val::Exp(0, *self) } +} + +impl Dsl for Ast { + type Str = Arc; + type Exp = Ast; + fn val (&self) -> Val { Val::Exp(0, self.clone()) } +} + +impl Dsl for Token { + type Str = Str; + type Exp = Exp; + fn val (&self) -> Val { self.value.clone() } +} + +impl Dsl for Val { + type Str = Str; + type Exp = Exp; + fn val (&self) -> Val { self.clone() } +} + +/// The expression representation for a [Dsl] implementation. +/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. +pub trait DslExp: PartialEq + Clone + Debug + Dsl {} +impl DslExp for T {} +/// The string representation for a [Dsl] implementation. +/// [Cst] uses `&'s str`. [Ast] uses `Arc`. +pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + Deref { + fn as_str (&self) -> &str { self.as_ref() } + fn as_arc (&self) -> Arc { self.as_ref().into() } +} +impl + Deref> DslStr for Str {} /// Enumeration of values that may figure in an expression. /// Generic over string and expression storage. #[derive(Clone, Debug, PartialEq, Default)] @@ -39,31 +113,20 @@ pub enum Val { Error(DslError), } impl Copy for Val {} -impl Val { - pub fn convert (&self) -> Val where - T::Str: for<'a> From<&'a Str>, - T::Exp: for<'a> From<&'a Exp> - { +impl Val { + pub fn convert_to (&self, to_str: impl Fn(&S1)->S2, to_exp: impl Fn(&E1)->E2) -> Val { match self { Val::Nil => Val::Nil, Val::Num(u) => Val::Num(*u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(*d, x.into()), + Val::Sym(s) => Val::Sym(to_str(s)), + Val::Key(s) => Val::Key(to_str(s)), + Val::Str(s) => Val::Str(to_str(s)), + Val::Exp(d, x) => Val::Exp(*d, to_exp(x)), Val::Error(e) => Val::Error(*e) } } } -/// The expression representation for a [Dsl] implementation. -/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. -pub trait DslExp: PartialEq + Clone + Default + Debug + Dsl {} -impl DslExp for T {} -/// The string representation for a [Dsl] implementation. -/// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + std::ops::Deref {} -impl + std::ops::Deref> DslStr for T {} -impl> Val { +impl> Val { pub const fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} pub const fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} @@ -85,7 +148,9 @@ pub struct Token { /// Length of span. pub length: usize, } +/// Tokens are copiable where possible. impl Copy for Token {} +/// Token methods. impl Token { pub const fn end (&self) -> usize { self.start.saturating_add(self.length) } @@ -99,83 +164,85 @@ impl Token { Self { value: self.value, ..*self } } } -/// To the [Dsl], a token is equivalent to its `value` field. -impl Dsl for Token { - type Str = Str; type Exp = Exp; - fn dsl (&self) -> Val { self.value.clone() } -} -/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. -pub trait Dsl: Clone + Debug { - /// The string representation for a dizzle. - type Str: DslStr; - /// The expression representation for a dizzle. - type Exp: DslExp; - /// Request the top-level DSL [Val]ue. - /// May perform cloning or parsing. - fn dsl (&self) -> Val; - fn err (&self) -> Option {self.dsl().err()} - fn nil (&self) -> bool {self.dsl().nil()} - fn num (&self) -> Option {self.dsl().num()} - fn sym (&self) -> Option {self.dsl().sym()} - fn key (&self) -> Option {self.dsl().key()} - fn str (&self) -> Option {self.dsl().str()} - fn exp (&self) -> Option {self.dsl().exp()} - fn exp_depth (&self) -> Option {self.dsl().exp_depth()} - fn exp_head (&self) -> Val {self.dsl().exp_head()} - fn exp_tail (&self) -> Self::Exp {self.dsl().exp_tail()} - fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() } -} -/// The most basic implementor of the [Dsl] trait. -impl Dsl for Val { - type Str = Str; type Exp = Exp; - fn dsl (&self) -> Val { self.clone() } -} -/// The abstract syntax tree (AST) can be produced from the CST -/// by cloning source slices into owned ([Arc]) string slices. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Ast(Arc, Ast>>>>); -pub type AstVal = Val, Ast>; -pub type AstToken = Token, Ast>; -impl Dsl for Ast { - type Str = Arc; type Exp = Ast; - fn dsl (&self) -> Val, Ast> { Val::Exp(0, Ast(self.0.clone())) } -} -impl<'s> From<&'s str> for Ast { - fn from (source: &'s str) -> Self { - let source: Arc = source.into(); - Self(CstIter(CstConstIter(source.as_ref())) - .map(|token|Arc::new(Token { - source: source.clone(), - start: token.start, - length: token.length, - value: match token.value { - Val::Nil => Val::Nil, - Val::Num(u) => Val::Num(u), - Val::Sym(s) => Val::Sym(s.into()), - Val::Key(s) => Val::Key(s.into()), - Val::Str(s) => Val::Str(s.into()), - Val::Exp(d, x) => Val::Exp(d, x.into()), - Val::Error(e) => Val::Error(e.into()) - }, - })) - .collect::>() - .into()) - } -} -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - let mut tokens: VecDeque<_> = Default::default(); - Self(tokens.into()) - } -} /// The concrete syntax tree (CST) implements zero-copy /// parsing of the DSL from a string reference. CST items /// preserve info about their location in the source. /// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub struct Cst<'s>(pub CstIter<'s>); -pub type CstVal<'s> = Val<&'s str, Cst<'s>>; -pub type CstToken<'s> = Token<&'s str, Cst<'s>>; +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub enum Cst<'s> { + #[default] __, + Source(&'s str), + Token(CstToken<'s>), + Val(CstVal<'s>), + Iter(CstIter<'s>), + ConstIter(CstConstIter<'s>), +} +impl<'s> From<&'s str> for Cst<'s> { + fn from (src: &'s str) -> Self { + Cst::Source(src) + } +} +impl<'s> From> for Cst<'s> { + fn from (token: CstToken<'s>) -> Self { + Cst::Token(token) + } +} +impl<'s> From> for Cst<'s> { + fn from (val: CstVal<'s>) -> Self { + Cst::Val(val) + } +} +impl<'s> From> for Cst<'s> { + fn from (iter: CstIter<'s>) -> Self { + Cst::Iter(iter) + } +} +impl<'s> From> for Cst<'s> { + fn from (iter: CstConstIter<'s>) -> Self { + Cst::ConstIter(iter) + } +} +impl<'s> From> for Ast { + fn from (cst: Cst<'s>) -> Self { + match cst { + Cst::Source(source) | Cst::Iter(CstIter(CstConstIter(source))) | + Cst::ConstIter(CstConstIter(source)) => + Ast::Source(source.into()), + Cst::Val(value) => + Ast::Val(value.convert_to(|s|(*s).into(), |e|Box::new((*e).into()))), + Cst::Token(Token { source, start, length, value }) => { + let source = AsRef::::as_ref(source).into(); + let value = value.convert_to(|s|(*s).into(), |e|Box::new((*e).into())); + Ast::Token(Token { source, start, length, value }) + }, + _ => unreachable!() + } + } +} +/// The abstract syntax tree (AST) can be produced from the CST +/// by cloning source slices into owned ([Arc]) string slices. +#[derive(Debug, Clone, PartialEq, Default)] +pub enum Ast { + #[default] __, + Source(Arc), + Token(Token, Box>), + Val(Val, Box>), + Exp(Arc, Ast>>>>), +} +impl<'s> From<&'s str> for Ast { + fn from (src: &'s str) -> Self { + Self::Source(src.into()) + } +} +impl<'s, Str: DslStr, Exp: DslExp + Into + 's> From> for Ast { + fn from (Token { source, start, length, value }: Token) -> Self { + let source: Arc = source.as_ref().into(); + let value = value.convert_to(|s|s.as_ref().into(), |e|Box::new(e.clone().into())); + Self::Token(Token { source, start, length, value }) + } +} +pub type CstVal<'s> = Val<&'s str, &'s str>; +pub type CstToken<'s> = Token<&'s str, &'s str>; impl<'s> CstToken<'s> { pub const fn slice (&self) -> &str { str_range(self.source, self.start, self.end()) } @@ -188,19 +255,32 @@ impl<'s> CstToken<'s> { self } pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { - self.value = Val::Exp(depth, Cst(CstIter(CstConstIter(source)))); + self.value = Val::Exp(depth, source); self } } -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; type Exp = Cst<'s>; - fn dsl (&self) -> Val { Val::Exp(0, Cst(self.0)) } -} -impl<'s> From<&'s str> for Cst<'s> { - fn from (source: &'s str) -> Self { - Self(CstIter(CstConstIter(source))) - } -} +//impl<'s> From<&'s str> for Ast { + //fn from (source: &'s str) -> Ast { + //let source: Arc = source.into(); + //Ast(CstIter(CstConstIter(source.as_ref())) + //.map(|token|Arc::new(Token { + //source: source.clone(), + //start: token.start, + //length: token.length, + //value: match token.value { + //Val::Nil => Val::Nil, + //Val::Num(u) => Val::Num(u), + //Val::Sym(s) => Val::Sym(s.into()), + //Val::Key(s) => Val::Key(s.into()), + //Val::Str(s) => Val::Str(s.into()), + //Val::Exp(d, x) => Val::Exp(d, x.into()), + //Val::Error(e) => Val::Error(e.into()) + //}, + //})) + //.collect::>() + //.into()) + //} +//} /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { #[error("parse failed: not implemented")] @@ -283,20 +363,12 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } start = i; length = 1; - if is_exp_start(c) { - value = Exp(1, Cst(CstIter(CstConstIter(str_range(source, i, i+1))))); - } else if is_str_start(c) { - value = Str(str_range(source, i, i+1)); - } else if is_sym_start(c) { - value = Sym(str_range(source, i, i+1)); - } else if is_key_start(c) { - value = Key(str_range(source, i, i+1)); - } else if is_digit(c) { - value = match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) }; - } else { - value = Error(Unexpected(c)); - break - } + value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) } + else if is_str_start(c) { Str(str_range(source, i, i+1)) } + else if is_sym_start(c) { Sym(str_range(source, i, i+1)) } + else if is_key_start(c) { Key(str_range(source, i, i+1)) } + else if is_digit(c) { match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) } } + else { value = Error(Unexpected(c)); break } } else if matches!(value, Str(_)) { if is_str_end(c) { break @@ -323,13 +395,13 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } } else if let Exp(depth, exp) = value { if depth == 0 { - value = Exp(0, Cst(CstIter(CstConstIter(str_range(source, start, start + length))))); + value = Exp(0, str_range(source, start, start + length)); break } length += 1; value = Exp( if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth }, - Cst(CstIter(CstConstIter(str_range(source, start, start + length)))) + str_range(source, start, start + length) ); } else if let Num(m) = value { if is_num_end(c) { @@ -378,22 +450,6 @@ pub const fn to_digit (c: char) -> Result { _ => return Err(Unexpected(c)) }) } -/// `State` + [Dsl] -> `Self`. -pub trait FromDsl: Sized { - fn try_from_dsl (state: &State, dsl: &impl Dsl) -> Perhaps; - fn from_dsl (state: &State, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_from_dsl(state, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } -} -/// `self` + `Options` -> [Dsl] -pub trait IntoDsl { /*TODO*/ } -/// `self` + [Dsl] -> `Item` -pub trait DslInto { - fn try_dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - match Self::try_dsl_into(self, dsl)? { Some(dsl) => Ok(dsl), _ => Err(err()) } } -} -/// `self` + `Item` -> [Dsl] -pub trait DslFrom { /*TODO*/ } /// Implement type conversions. macro_rules! from(($($Struct:ty { $( @@ -421,3 +477,53 @@ macro_rules! from(($($Struct:ty { $( //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } //} + +/// `T` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; + fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or(err) + } + fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or_else(err) + } +} + +impl, U> DslInto for U { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + T::from_dsl(self, dsl) + } +} + +/// `self` + [Dsl] -> `T` +pub trait DslInto { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_into(dsl)?.ok_or(err) + } + fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_into(dsl)?.ok_or_else(err) + } +} + +/// `self` + `T` + -> [Dsl] +pub trait IntoDsl { + fn into_dsl (&self, state: &T) -> Perhaps; + fn into_dsl_or (&self, state: &T, err: Box) -> Usually { + self.into_dsl(state)?.ok_or(err) + } + fn into_dsl_or_else (&self, state: &T, err: impl Fn()->Box) -> Usually { + self.into_dsl(state)?.ok_or_else(err) + } +} + +/// `self` + `T` -> [Dsl] +pub trait DslFrom { + fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_from_or (&self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_from(dsl)?.ok_or(err) + } + fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_from(dsl)?.ok_or_else(err) + } +} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs new file mode 100644 index 0000000..e69de29 diff --git a/dsl/src/test.rs b/dsl/src/test.rs deleted file mode 100644 index dfb37f4..0000000 --- a/dsl/src/test.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::*; -use proptest::prelude::*; - -#[test] fn test_iters () { - let mut iter = crate::CstIter::new(&":foo :bar"); - let _ = iter.next(); -} - -#[test] const fn test_const_iters () { - let iter = crate::CstConstIter::new(&":foo :bar"); - let _ = iter.next(); -} - -#[test] fn test_num () { - let _digit = Token::to_digit('0'); - let _digit = Token::to_digit('x'); - let _number = Token::to_number(&"123"); - let _number = Token::to_number(&"12asdf3"); -} - //proptest! { - //#[test] fn proptest_source_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::CstIter::new(&source); - ////let _ = iter.next() - //} - //#[test] fn proptest_token_iter ( - //source in "\\PC*" - //) { - //let mut iter = crate::TokenIter::new(&source); - ////let _ = iter.next(); - //} - //} - -//#[cfg(test)] mod test_token_prop { - //use crate::{Cst, CstMeta, Value::*}; - //use proptest::prelude::*; - //proptest! { - //#[test] fn test_token_prop ( - //source in "\\PC*", - //start in usize::MIN..usize::MAX, - //length in usize::MIN..usize::MAX, - //) { - //let token = Cst(Nil, CstMeta { source: &source, start, length }); - //let _ = token.slice(); - //} - //} -//} - -#[test] fn test_token () -> Result<(), Box> { - use crate::Val::*; - let source = ":f00"; - let mut token = CstToken::new(source, 0, 1, Sym(":")); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 2, Sym(":f"))); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 3, Sym(":f0"))); - token = token.grow_sym(); - assert_eq!(token, CstToken::new(source, 0, 4, Sym(":f00"))); - - assert_eq!(None, CstIter::new("").next()); - assert_eq!(None, CstIter::new(" \n \r \t ").next()); - - assert_eq!(Err(Unexpected('a')), CstIter::new(" 9a ").next().unwrap().value()); - - assert_eq!(Num(7), CstIter::new("7").next().unwrap().value()); - assert_eq!(Num(100), CstIter::new(" 100 ").next().unwrap().value()); - - assert_eq!(Sym(":123foo"), CstIter::new(" :123foo ").next().unwrap().value()); - assert_eq!(Sym("@bar456"), CstIter::new(" \r\r\n\n@bar456\t\t\t").next().unwrap().value()); - - assert_eq!(Key("foo123"), CstIter::new("foo123").next().unwrap().value()); - assert_eq!(Key("foo/bar"), CstIter::new("foo/bar").next().unwrap().value()); - - //assert_eq!(Str("foo/bar"), CstIter::new("\"foo/bar\"").next().unwrap().value()); - //assert_eq!(Str("foo/bar"), CstIter::new(" \"foo/bar\" ").next().unwrap().value()); - - Ok(()) -} - -//#[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. - //let mut view = TokenIter(source); - //// The token iterator wraps a const token+source iterator. - //assert_eq!(view.0.0, source); - //let mut expr = view.peek(); - //assert_eq!(view.0.0, source); - //assert_eq!(expr, Some(Token { - //source, start: 0, length: source.len() - 1, value: Exp(0, CstIter::new(&source[1..])) - //})); - ////panic!("{view:?}"); - ////panic!("{:#?}", expr); - ////for example in [ - ////include_str!("../../tui/examples/edn01.edn"), - ////include_str!("../../tui/examples/edn02.edn"), - ////] { - //////let items = Dsl::read_all(example)?; - //////panic!("{layout:?}"); - //////let content = >::from(&layout); - ////} - //Ok(()) -//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 5032319..5403b66 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -7,7 +7,7 @@ use crate::*; /// /// When a key is pressed, the bindings for it are checked in sequence. /// When the first non-conditional or true conditional binding is executed, -/// that binding's value is returned. +/// that .event()binding's value is returned. #[derive(Debug)] pub struct EventMap( /// Map of each event (e.g. key combination) to /// all command expressions bound to it by @@ -71,7 +71,7 @@ impl> + Clone> EventMap { } /// Evaluate the active layers for a given state, /// returning the command to be executed, if any. - pub fn handle (&self, state: &mut S, event: Ast) -> Perhaps where + pub fn handle (&self, state: &mut S, event: &E) -> Perhaps where S: DslInto + DslInto, O: Command { diff --git a/output/src/lib.rs b/output/src/lib.rs index 45abdda..334c418 100644 --- a/output/src/lib.rs +++ b/output/src/lib.rs @@ -3,6 +3,9 @@ #![feature(impl_trait_in_assoc_type)] pub(crate) use std::marker::PhantomData; +pub(crate) use std::fmt::{Debug, Display}; +pub(crate) use std::ops::{Add, Sub, Mul, Div}; +pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; pub(crate) use tengri_core::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; diff --git a/output/src/ops.rs b/output/src/ops.rs index 976ab51..846c86c 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -48,32 +48,21 @@ use crate::*; use Direction::*; +use std::marker::PhantomData; +use std::sync::{Arc, RwLock}; -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 { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} -} - +/// Stack things on top of each other, +#[macro_export] macro_rules! lay (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}); /// Stack southward. -#[macro_export] macro_rules! col { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}; -} - +#[macro_export] macro_rules! col (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::s(bsp, $expr);)*; bsp }}); /// Stack northward. -#[macro_export] macro_rules! col_up { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }} -} - +#[macro_export] macro_rules! col_up (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::n(bsp, $expr);)*; bsp }}); /// Stack eastward. -#[macro_export] macro_rules! row { - ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}; -} - +#[macro_export] macro_rules! row (($($expr:expr),* $(,)?) => + {{ let bsp = (); $(let bsp = Bsp::e(bsp, $expr);)*; bsp }}); /// Show an item only when a condition is true. pub struct When(pub bool, pub A); impl When { @@ -408,7 +397,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_from_dsl ( + fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() @@ -423,7 +412,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ $op:literal $(/)? [$head: ident, $tail: ident] $expr:expr ) => { impl FromDsl for $Struct$(<$($A),+>)? { - fn try_from_dsl ( + fn from_dsl ( _state: &S, _dsl: &impl Dsl ) -> Perhaps { todo!() @@ -686,3 +675,353 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ //_ => None //}) } + +/// Lazily-evaluated [Render]able. +pub struct Thunk, F: Fn()->T>( + PhantomData, + F +); +impl, F: Fn()->T> Thunk { + pub const fn new (thunk: F) -> Self { + Self(PhantomData, thunk) + } +} +impl, F: Fn()->T> Content for Thunk { + fn content (&self) -> impl Render { (self.1)() } +} + +pub struct ThunkBox( + PhantomData, + BoxBox>>, +); +impl ThunkBox { + pub const fn new (thunk: BoxBox>>) -> Self { + Self(PhantomData, thunk) + } +} +impl Content for ThunkBox { + fn content (&self) -> impl Render { (&self.1)() } +} +impl FromBox>>> for ThunkBox { + fn from (f: BoxBox>>) -> Self { + Self(PhantomData, f) + } +} +//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { + //fn from (f: F) -> Self { + //Self(Default::default(), Box::new(f)) + //} +//} + +pub struct ThunkRender(PhantomData, F); +impl ThunkRender { + pub fn new (render: F) -> Self { Self(PhantomData, render) } +} +impl Content for ThunkRender { + fn render (&self, to: &mut E) { (self.1)(to) } +} + +pub struct ThunkLayout< + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +>( + PhantomData, + F1, + F2 +); +implE::Area, F2: Fn(&mut E)> ThunkLayout { + pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } +} +impl Content for ThunkLayout +where + E: Output, + F1: Fn(E::Area)->E::Area, + F2: Fn(&mut E) +{ + fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } + fn render (&self, to: &mut E) { (self.2)(to) } +} + +#[derive(Debug, Default)] pub struct Memo { + pub value: T, + pub view: Arc> +} + +impl Memo { + pub fn new (value: T, view: U) -> Self { + Self { value, view: Arc::new(view.into()) } + } + pub fn update ( + &mut self, + newval: T, + render: impl Fn(&mut U, &T, &T)->R + ) -> Option { + if newval != self.value { + let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); + self.value = newval; + return Some(result); + } + None + } +} + +/// Clear a pre-allocated buffer, then write into it. +#[macro_export] macro_rules! rewrite { + ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } +} + +pub struct Stack { + __: PhantomData, + direction: Direction, + callback: F +} +impl Stack { + pub fn new (direction: Direction, callback: F) -> Self { + Self { direction, callback, __: Default::default(), } + } + pub fn north (callback: F) -> Self { + Self::new(North, callback) + } + pub fn south (callback: F) -> Self { + Self::new(South, callback) + } + pub fn east (callback: F) -> Self { + Self::new(East, callback) + } + pub fn west (callback: F) -> Self { + Self::new(West, callback) + } +} +impl)->())->()> Content for Stack { + fn layout (&self, to: E::Area) -> E::Area { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); + match self.direction { + South => { + y = y.plus(h); + h_used = h_used.plus(h); + h_remaining = h_remaining.minus(h); + w_used = w_used.max(w); + }, + East => { + x = x.plus(w); + w_used = w_used.plus(w); + w_remaining = w_remaining.minus(w); + h_used = h_used.max(h); + }, + North | West => { + todo!() + }, + _ => unreachable!(), + } + }); + match self.direction { + North | West => { + todo!() + }, + South | East => { + [to.x(), to.y(), w_used.into(), h_used.into()].into() + }, + _ => unreachable!(), + } + } + fn render (&self, to: &mut E) { + let mut x = to.x(); + let mut y = to.y(); + let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); + let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); + (self.callback)(&mut move |component: &dyn Render|{ + let layout = component.layout([x, y, w_remaining, h_remaining].into()); + match self.direction { + South => { + y = y.plus(layout.h()); + h_remaining = h_remaining.minus(layout.h()); + h_used = h_used.plus(layout.h()); + to.place(layout, component); + }, + East => { + x = x.plus(layout.w()); + w_remaining = w_remaining.minus(layout.w()); + w_used = w_used.plus(layout.h()); + to.place(layout, component); + }, + North | West => { + todo!() + }, + _ => unreachable!() + } + }); + } +} + +/*Stack::down(|add|{ + let mut i = 0; + for (_, name) in self.dirs.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + for (_, name) in self.files.iter() { + if i >= self.scroll { + add(&Tui::bold(i == self.index, name.as_str()))?; + } + i += 1; + } + add(&format!("{}/{i}", self.index))?; + Ok(()) +}));*/ + +/// Renders items from an iterator. +pub struct Map +where + I: Iterator + Send + Sync, + F: Fn() -> I + Send + Sync, +{ + __: PhantomData<(E, B)>, + /// Function that returns iterator over stacked components + get_iter: F, + /// Function that returns each stacked component + get_item: G, +} + +impl<'a, E, A, B, I, F, G> Map where + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, +{ + pub const fn new (get_iter: F, get_item: G) -> Self { + Self { + __: PhantomData, + get_iter, + get_item + } + } +} + +impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> +where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a +{ + pub const fn east ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) + } + } + } + + pub const fn south ( + size: E::Unit, + get_iter: F, + get_item: impl Fn(A, usize)->B + Send + Sync + ) -> Map< + E, A, + Push>>, + I, F, + impl Fn(A, usize)->Push>> + Send + Sync + > where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a + { + Map { + __: PhantomData, + get_iter, + get_item: move |item: A, index: usize|{ + // FIXME: multiply + let mut push: E::Unit = E::Unit::from(0u16); + for _ in 0..index { + push = push + size; + } + Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) + } + } + } +} + +impl<'a, E, A, B, I, F, G> Content for Map where + E: Output, + B: Render, + I: Iterator + Send + Sync + 'a, + F: Fn() -> I + Send + Sync + 'a, + G: Fn(A, usize)->B + Send + Sync +{ + fn layout (&self, area: E::Area) -> E::Area { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let [mut min_x, mut min_y] = area.center(); + let [mut max_x, mut max_y] = area.center(); + for item in get_iter() { + let [x,y,w,h] = get_item(item, index).layout(area).xywh(); + min_x = min_x.min(x.into()); + min_y = min_y.min(y.into()); + max_x = max_x.max((x + w).into()); + max_y = max_y.max((y + h).into()); + index += 1; + } + let w = max_x - min_x; + let h = max_y - min_y; + //[min_x.into(), min_y.into(), w.into(), h.into()].into() + area.center_xy([w.into(), h.into()].into()).into() + } + fn render (&self, to: &mut E) { + let Self { get_iter, get_item, .. } = self; + let mut index = 0; + let area = Content::layout(self, to.area()); + for item in get_iter() { + let item = get_item(item, index); + //to.place(area.into(), &item); + to.place(item.layout(area), &item); + index += 1; + } + } +} + +#[inline] pub fn map_south( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) +} + +#[inline] pub fn map_south_west( + item_offset: O::Unit, + item_height: O::Unit, + item: impl Content +) -> impl Content { + Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) +} + +#[inline] pub fn map_east( + item_offset: O::Unit, + item_width: O::Unit, + item: impl Content +) -> impl Content { + Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) +} diff --git a/output/src/ops/_collect.rs b/output/src/ops/_collect.rs deleted file mode 100644 index 5559afe..0000000 --- a/output/src/ops/_collect.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Groupings of elements. -use crate::*; - -/// A function or closure that emits renderables. -pub trait Collector: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. -impl Collector for F -where E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)) {} - -pub trait Render { - fn area (&self, to: E::Area) -> E::Area; - fn render (&self, to: &mut E::Output); -} - -impl> Render for C { - fn area (&self, to: E::Area) -> E::Area { - Content::area(self, to) - } - fn render (&self, to: &mut E::Output) { - Content::render(self, to) - } -} diff --git a/output/src/ops/_reduce.rs b/output/src/ops/_reduce.rs deleted file mode 100644 index dfcc00e..0000000 --- a/output/src/ops/_reduce.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::*; - -pub struct Reduce(pub PhantomData, pub F, pub G) where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync; - -impl Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - pub const fn new (f: F, g: G) -> Self { Self(Default::default(), f, g) } -} - -impl Content for Reduce where - A: Send + Sync, B: Send + Sync, - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, - G: Fn(A, B, usize)->A + Send + Sync -{ - fn content (&self) -> impl Render { - } -} - -/* - - //pub fn reduce (iterator: I, callback: F) -> Reduce where - //E: Output, - //I: Iterator + Send + Sync, - //R: Render, - //F: Fn(R, T, usize) -> R + Send + Sync - //{ - //Reduce(Default::default(), iterator, callback) - //} -pub struct Reduce(PhantomData<(E, R)>, I, F) where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync; -impl Content for Reduce where - E: Output, - I: Iterator + Send + Sync, - R: Render, - F: Fn(R, T, usize) -> R + Send + Sync -{ - fn render (&self, to: &mut E) { - todo!() - } -} -*/ - -//macro_rules! define_ops { - //($Trait:ident<$E:ident:$Output:path> { $( - //$(#[$attr:meta $($attr_args:tt)*])* - //( - //$fn:ident - //$(<$($G:ident$(:$Gen:path)?, )+>)? - //$Op:ident - //($($arg:ident:$Arg:ty),*) - //) - //)* }) => { - //impl<$E: $Output> $Trait for E {} - //pub trait $Trait<$E: $Output> { - //$( - //$(#[$attr $($attr_args)*])* - //fn $fn $(<$($G),+>)? - //($($arg:$Arg),*)-> $Op<$($(, $G)+)?> - //$(where $($G: $($Gen + Send + Sync)?),+)? - //{ $Op($($arg),*) } - //)* - //} - //} -//} - -//define_ops! { - //Layout { - //(when ,> - //When(cond: bool, item: A)) - ///// When `cond` is `true`, render `a`, otherwise render `b`. - //(either , B: Render,> - //Either(cond: bool, a: A, b: B)) - ///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing. - //(opt B, B: Render,> - //Opt(option: Option, cb: F)) - ///// Maps items of iterator through callback. - //(map , I: Iterator, F: Fn() -> I, G: Fn(A, usize)->B,> - //Map(get_iterator: F, callback: G)) - //} -//} - diff --git a/output/src/ops/map.rs b/output/src/ops/map.rs deleted file mode 100644 index d124569..0000000 --- a/output/src/ops/map.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::*; - -/// Renders items from an iterator. -pub struct Map -where - I: Iterator + Send + Sync, - F: Fn() -> I + Send + Sync, -{ - __: PhantomData<(E, B)>, - /// Function that returns iterator over stacked components - get_iter: F, - /// Function that returns each stacked component - get_item: G, -} - -impl<'a, E, A, B, I, F, G> Map where - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, -{ - pub const fn new (get_iter: F, get_item: G) -> Self { - Self { - __: PhantomData, - get_iter, - get_item - } - } -} - -impl<'a, E, A, B, I, F> Map>>>, I, F, fn(A, usize)->B> -where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a -{ - pub const fn east ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::x(push, Align::w(Fixed::x(size, get_item(item, index)))) - } - } - } - - pub const fn south ( - size: E::Unit, - get_iter: F, - get_item: impl Fn(A, usize)->B + Send + Sync - ) -> Map< - E, A, - Push>>, - I, F, - impl Fn(A, usize)->Push>> + Send + Sync - > where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a - { - Map { - __: PhantomData, - get_iter, - get_item: move |item: A, index: usize|{ - // FIXME: multiply - let mut push: E::Unit = E::Unit::from(0u16); - for _ in 0..index { - push = push + size; - } - Push::y(push, Align::n(Fixed::y(size, get_item(item, index)))) - } - } - } -} - -impl<'a, E, A, B, I, F, G> Content for Map where - E: Output, - B: Render, - I: Iterator + Send + Sync + 'a, - F: Fn() -> I + Send + Sync + 'a, - G: Fn(A, usize)->B + Send + Sync -{ - fn layout (&self, area: E::Area) -> E::Area { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let [mut min_x, mut min_y] = area.center(); - let [mut max_x, mut max_y] = area.center(); - for item in get_iter() { - let [x,y,w,h] = get_item(item, index).layout(area).xywh(); - min_x = min_x.min(x.into()); - min_y = min_y.min(y.into()); - max_x = max_x.max((x + w).into()); - max_y = max_y.max((y + h).into()); - index += 1; - } - let w = max_x - min_x; - let h = max_y - min_y; - //[min_x.into(), min_y.into(), w.into(), h.into()].into() - area.center_xy([w.into(), h.into()].into()).into() - } - fn render (&self, to: &mut E) { - let Self { get_iter, get_item, .. } = self; - let mut index = 0; - let area = Content::layout(self, to.area()); - for item in get_iter() { - let item = get_item(item, index); - //to.place(area.into(), &item); - to.place(item.layout(area), &item); - index += 1; - } - } -} - -#[inline] pub fn map_south( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Fixed::y(item_height, Fill::x(item))) -} - -#[inline] pub fn map_south_west( - item_offset: O::Unit, - item_height: O::Unit, - item: impl Content -) -> impl Content { - Push::y(item_offset, Align::nw(Fixed::y(item_height, Fill::x(item)))) -} - -#[inline] pub fn map_east( - item_offset: O::Unit, - item_width: O::Unit, - item: impl Content -) -> impl Content { - Push::x(item_offset, Align::w(Fixed::x(item_width, Fill::y(item)))) -} diff --git a/output/src/ops/memo.rs b/output/src/ops/memo.rs deleted file mode 100644 index 8168f2d..0000000 --- a/output/src/ops/memo.rs +++ /dev/null @@ -1,30 +0,0 @@ -//use crate::*; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, Default)] pub struct Memo { - pub value: T, - pub view: Arc> -} - -impl Memo { - pub fn new (value: T, view: U) -> Self { - Self { value, view: Arc::new(view.into()) } - } - pub fn update ( - &mut self, - newval: T, - render: impl Fn(&mut U, &T, &T)->R - ) -> Option { - if newval != self.value { - let result = render(&mut*self.view.write().unwrap(), &newval, &self.value); - self.value = newval; - return Some(result); - } - None - } -} - -/// Clear a pre-allocated buffer, then write into it. -#[macro_export] macro_rules! rewrite { - ($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } } -} diff --git a/output/src/ops/stack.rs b/output/src/ops/stack.rs deleted file mode 100644 index 59930ce..0000000 --- a/output/src/ops/stack.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::*; -use Direction::*; - -pub struct Stack { - __: PhantomData, - direction: Direction, - callback: F -} -impl Stack { - pub fn new (direction: Direction, callback: F) -> Self { - Self { direction, callback, __: Default::default(), } - } - pub fn north (callback: F) -> Self { - Self::new(North, callback) - } - pub fn south (callback: F) -> Self { - Self::new(South, callback) - } - pub fn east (callback: F) -> Self { - Self::new(East, callback) - } - pub fn west (callback: F) -> Self { - Self::new(West, callback) - } -} -impl)->())->()> Content for Stack { - fn layout (&self, to: E::Area) -> E::Area { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); - match self.direction { - South => { - y = y.plus(h); - h_used = h_used.plus(h); - h_remaining = h_remaining.minus(h); - w_used = w_used.max(w); - }, - East => { - x = x.plus(w); - w_used = w_used.plus(w); - w_remaining = w_remaining.minus(w); - h_used = h_used.max(h); - }, - North | West => { - todo!() - }, - _ => unreachable!(), - } - }); - match self.direction { - North | West => { - todo!() - }, - South | East => { - [to.x(), to.y(), w_used.into(), h_used.into()].into() - }, - _ => unreachable!(), - } - } - fn render (&self, to: &mut E) { - let mut x = to.x(); - let mut y = to.y(); - let (mut w_used, mut w_remaining) = (E::Unit::zero(), to.w()); - let (mut h_used, mut h_remaining) = (E::Unit::zero(), to.h()); - (self.callback)(&mut move |component: &dyn Render|{ - let layout = component.layout([x, y, w_remaining, h_remaining].into()); - match self.direction { - South => { - y = y.plus(layout.h()); - h_remaining = h_remaining.minus(layout.h()); - h_used = h_used.plus(layout.h()); - to.place(layout, component); - }, - East => { - x = x.plus(layout.w()); - w_remaining = w_remaining.minus(layout.w()); - w_used = w_used.plus(layout.h()); - to.place(layout, component); - }, - North | West => { - todo!() - }, - _ => unreachable!() - } - }); - } -} - -/*Stack::down(|add|{ - let mut i = 0; - for (_, name) in self.dirs.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - for (_, name) in self.files.iter() { - if i >= self.scroll { - add(&Tui::bold(i == self.index, name.as_str()))?; - } - i += 1; - } - add(&format!("{}/{i}", self.index))?; - Ok(()) -}));*/ - diff --git a/output/src/ops/thunk.rs b/output/src/ops/thunk.rs deleted file mode 100644 index d0e2877..0000000 --- a/output/src/ops/thunk.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::*; -use std::marker::PhantomData; - -/// Lazily-evaluated [Render]able. -pub struct Thunk, F: Fn()->T>( - PhantomData, - F -); -impl, F: Fn()->T> Thunk { - pub const fn new (thunk: F) -> Self { - Self(PhantomData, thunk) - } -} -impl, F: Fn()->T> Content for Thunk { - fn content (&self) -> impl Render { (self.1)() } -} - -pub struct ThunkBox( - PhantomData, - BoxBox>>, -); -impl ThunkBox { - pub const fn new (thunk: BoxBox>>) -> Self { - Self(PhantomData, thunk) - } -} -impl Content for ThunkBox { - fn content (&self) -> impl Render { (&self.1)() } -} -impl FromBox>>> for ThunkBox { - fn from (f: BoxBox>>) -> Self { - Self(PhantomData, f) - } -} -//impl<'a, E: Output, F: Fn()->Box + 'a> + 'a> From for ThunkBox<'a, E> { - //fn from (f: F) -> Self { - //Self(Default::default(), Box::new(f)) - //} -//} - -pub struct ThunkRender(PhantomData, F); -impl ThunkRender { - pub fn new (render: F) -> Self { Self(PhantomData, render) } -} -impl Content for ThunkRender { - fn render (&self, to: &mut E) { (self.1)(to) } -} - -pub struct ThunkLayout< - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) ->( - PhantomData, - F1, - F2 -); -implE::Area, F2: Fn(&mut E)> ThunkLayout { - pub fn new (layout: F1, render: F2) -> Self { Self(PhantomData, layout, render) } -} -impl Content for ThunkLayout -where - E: Output, - F1: Fn(E::Area)->E::Area, - F2: Fn(&mut E) -{ - fn layout (&self, to: E::Area) -> E::Area { (self.1)(to) } - fn render (&self, to: &mut E) { (self.2)(to) } -} diff --git a/output/src/space.rs b/output/src/space.rs index 056e12f..3e1298b 100644 --- a/output/src/space.rs +++ b/output/src/space.rs @@ -1,5 +1,274 @@ -mod area; pub use self::area::*; -mod coordinate; pub use self::coordinate::*; -mod direction; pub use self::direction::*; -mod measure; pub use self::measure::*; -mod size; pub use self::size::*; +use crate::*; +use Direction::*; + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Direction { + North, South, East, West, Above, Below +} + +impl Direction { + pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { + let [x, y, w, h] = area.xywh(); + match self { + North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), + South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), + East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), + West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), + Above | Below => (area.xywh(), area.xywh()) + } + } +} + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn zero () -> Self { 0.into() } + fn plus (self, other: Self) -> Self; + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } +} + +impl Coordinate for u16 { + fn plus (self, other: Self) -> Self { + self.saturating_add(other) + } +} + +pub trait Area: From<[N;4]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + fn zero () -> [N;4] { + [N::zero(), N::zero(), N::zero(), N::zero()] + } + fn from_position (pos: impl Size) -> [N;4] { + let [x, y] = pos.wh(); + [x, y, 0.into(), 0.into()] + } + fn from_size (size: impl Size) -> [N;4] { + let [w, h] = size.wh(); + [0.into(), 0.into(), w, h] + } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn xy (&self) -> [N;2] { + [self.x(), self.y()] + } + fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } + fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + fn x2 (&self) -> N { + self.x().plus(self.w()) + } + fn y2 (&self) -> N { + self.y().plus(self.h()) + } + fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + fn center (&self) -> [N;2] { + [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] + } + fn center_x (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] + } + fn center_y (&self, n: N) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] + } + fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { + let [x, y, w, h] = self.xywh(); + [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] + } + fn centered (&self) -> [N;2] { + [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] + } + fn iter_x (&self) -> impl Iterator where N: std::iter::Step { + self.x()..(self.x()+self.w()) + } + fn iter_y (&self) -> impl Iterator where N: std::iter::Step { + self.y()..(self.y()+self.h()) + } +} + +impl Area for (N, N, N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } + fn w (&self) -> N { self.2 } + fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } + fn w (&self) -> N { self[2] } + fn h (&self) -> N { self[3] } +} + +pub trait Size: From<[N;2]> + Debug + Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N { self.x() } + fn h (&self) -> N { self.y() } + fn wh (&self) -> [N;2] { [self.x(), self.y()] } + fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + fn zero () -> [N;2] { + [N::zero(), N::zero()] + } + fn to_area_pos (&self) -> [N;4] { + let [x, y] = self.wh(); + [x, y, 0.into(), 0.into()] + } + fn to_area_size (&self) -> [N;4] { + let [w, h] = self.wh(); + [0.into(), 0.into(), w, h] + } +} + +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } +} + +pub trait HasSize { + fn size (&self) -> &Measure; + fn width (&self) -> usize { + self.size().w() + } + fn height (&self) -> usize { + self.size().h() + } +} + +impl>> HasSize for T { + fn size (&self) -> &Measure { + self.get() + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small +impl Content for Measure { + fn render (&self, to: &mut E) { + self.x.store(to.area().w().into(), Relaxed); + self.y.store(to.area().h().into(), Relaxed); + } +} + +impl Clone for Measure { + fn clone (&self) -> Self { + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } + } +} + +impl std::fmt::Debug for Measure { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("Measure") + .field("width", &self.x) + .field("height", &self.y) + .finish() + } +} + +impl Measure { + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } + pub fn set_w (&self, w: impl Into) -> &Self { + self.x.store(w.into(), Relaxed); + self + } + pub fn set_h (&self, h: impl Into) -> &Self { + self.y.store(h.into(), Relaxed); + self + } + pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { + self.set_w(w); + self.set_h(h); + self + } + pub fn w (&self) -> usize { + self.x.load(Relaxed) + } + pub fn h (&self) -> usize { + self.y.load(Relaxed) + } + pub fn wh (&self) -> [usize;2] { + [self.w(), self.h()] + } + pub fn format (&self) -> Arc { + format!("{}x{}", self.w(), self.h()).into() + } + pub fn of > (&self, item: T) -> Bsp, T> { + Bsp::b(Fill::xy(self), item) + } +} diff --git a/output/src/space/area.rs b/output/src/space/area.rs deleted file mode 100644 index 4393790..0000000 --- a/output/src/space/area.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Area: From<[N;4]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N; - fn h (&self) -> N; - fn zero () -> [N;4] { - [N::zero(), N::zero(), N::zero(), N::zero()] - } - fn from_position (pos: impl Size) -> [N;4] { - let [x, y] = pos.wh(); - [x, y, 0.into(), 0.into()] - } - fn from_size (size: impl Size) -> [N;4] { - let [w, h] = size.wh(); - [0.into(), 0.into(), w, h] - } - fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } - fn xy (&self) -> [N;2] { - [self.x(), self.y()] - } - fn wh (&self) -> [N;2] { - [self.w(), self.h()] - } - fn xywh (&self) -> [N;4] { - [self.x(), self.y(), self.w(), self.h()] - } - fn clip_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), self.h().min(h)] - } - fn clip_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), self.w().min(w), self.h()] - } - fn clip (&self, wh: impl Size) -> [N;4] { - [self.x(), self.y(), wh.w(), wh.h()] - } - fn set_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), w, self.h()] - } - fn set_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), h] - } - fn x2 (&self) -> N { - self.x().plus(self.w()) - } - fn y2 (&self) -> N { - self.y().plus(self.h()) - } - fn lrtb (&self) -> [N;4] { - [self.x(), self.x2(), self.y(), self.y2()] - } - fn center (&self) -> [N;2] { - [self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into())] - } - fn center_x (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into()] - } - fn center_y (&self, n: N) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n] - } - fn center_xy (&self, [n, m]: [N;2]) -> [N;4] { - let [x, y, w, h] = self.xywh(); - [(x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m] - } - fn centered (&self) -> [N;2] { - [self.x().minus(self.w()/2.into()), self.y().minus(self.h()/2.into())] - } - fn iter_x (&self) -> impl Iterator where N: std::iter::Step { - self.x()..(self.x()+self.w()) - } - fn iter_y (&self) -> impl Iterator where N: std::iter::Step { - self.y()..(self.y()+self.h()) - } -} - -impl Area for (N, N, N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } - fn w (&self) -> N { self.2 } - fn h (&self) -> N { self.3 } -} - -impl Area for [N;4] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } - fn w (&self) -> N { self[2] } - fn h (&self) -> N { self[3] } -} diff --git a/output/src/space/coordinate.rs b/output/src/space/coordinate.rs deleted file mode 100644 index 262be9e..0000000 --- a/output/src/space/coordinate.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::ops::{Add, Sub, Mul, Div}; - -/// A linear coordinate. -pub trait Coordinate: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{ - fn zero () -> Self { 0.into() } - fn plus (self, other: Self) -> Self; - fn minus (self, other: Self) -> Self { - if self >= other { - self - other - } else { - 0.into() - } - } -} - -impl Coordinate for u16 { - fn plus (self, other: Self) -> Self { - self.saturating_add(other) - } -} diff --git a/output/src/space/direction.rs b/output/src/space/direction.rs deleted file mode 100644 index ed9df46..0000000 --- a/output/src/space/direction.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::*; -use crate::Direction::*; - -/// A cardinal direction. -#[derive(Copy, Clone, PartialEq, Debug)] -#[cfg_attr(test, derive(Arbitrary))] -pub enum Direction { - North, South, East, West, Above, Below -} - -impl Direction { - pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { - let [x, y, w, h] = area.xywh(); - match self { - North => ([x, y.plus(h).minus(a), w, a], [x, y, w, h.minus(a)]), - South => ([x, y, w, a], [x, y.plus(a), w, h.minus(a)]), - East => ([x, y, a, h], [x.plus(a), y, w.minus(a), h]), - West => ([x.plus(w).minus(a), y, a, h], [x, y, w.minus(a), h]), - Above | Below => (area.xywh(), area.xywh()) - } - } -} diff --git a/output/src/space/measure.rs b/output/src/space/measure.rs deleted file mode 100644 index 9ed5c2c..0000000 --- a/output/src/space/measure.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::*; -use std::sync::{Arc, atomic::{AtomicUsize, Ordering::Relaxed}}; - -pub trait HasSize { - fn size (&self) -> &Measure; - fn width (&self) -> usize { - self.size().w() - } - fn height (&self) -> usize { - self.size().h() - } -} - -impl>> HasSize for T { - fn size (&self) -> &Measure { - self.get() - } -} - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - -// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small -impl Content for Measure { - fn render (&self, to: &mut E) { - self.x.store(to.area().w().into(), Relaxed); - self.y.store(to.area().h().into(), Relaxed); - } -} - -impl Clone for Measure { - fn clone (&self) -> Self { - Self { - _engine: Default::default(), - x: self.x.clone(), - y: self.y.clone(), - } - } -} - -impl std::fmt::Debug for Measure { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("Measure") - .field("width", &self.x) - .field("height", &self.y) - .finish() - } -} - -impl Measure { - pub fn new () -> Self { - Self { - _engine: PhantomData::default(), - x: Arc::new(0.into()), - y: Arc::new(0.into()), - } - } - pub fn set_w (&self, w: impl Into) -> &Self { - self.x.store(w.into(), Relaxed); - self - } - pub fn set_h (&self, h: impl Into) -> &Self { - self.y.store(h.into(), Relaxed); - self - } - pub fn set_wh (&self, w: impl Into, h: impl Into) -> &Self { - self.set_w(w); - self.set_h(h); - self - } - pub fn w (&self) -> usize { - self.x.load(Relaxed) - } - pub fn h (&self) -> usize { - self.y.load(Relaxed) - } - pub fn wh (&self) -> [usize;2] { - [self.w(), self.h()] - } - pub fn format (&self) -> Arc { - format!("{}x{}", self.w(), self.h()).into() - } - pub fn of > (&self, item: T) -> Bsp, T> { - Bsp::b(Fill::xy(self), item) - } -} diff --git a/output/src/space/size.rs b/output/src/space/size.rs deleted file mode 100644 index f9e2d19..0000000 --- a/output/src/space/size.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::*; -use std::fmt::Debug; - -pub trait Size: From<[N;2]> + Debug + Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N { self.x() } - fn h (&self) -> N { self.y() } - fn wh (&self) -> [N;2] { [self.x(), self.y()] } - fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } - fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } - fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } - fn zero () -> [N;2] { - [N::zero(), N::zero()] - } - fn to_area_pos (&self) -> [N;4] { - let [x, y] = self.wh(); - [x, y, 0.into(), 0.into()] - } - fn to_area_size (&self) -> [N;4] { - let [w, h] = self.wh(); - [0.into(), 0.into(), w, h] - } -} - -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} - -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index 36b25d5..d0db6e3 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -150,10 +150,9 @@ impl ToTokens for CommandDef { } /// Generated by [tengri_proc::command]. impl ::tengri::dsl::FromDsl<#state> for #command_enum { - fn try_from_dsl ( - state: &#state, - value: &impl ::tengri::dsl::Dsl - ) -> Perhaps { + fn from_dsl (state: &#state, value: &impl ::tengri::dsl::Dsl) + -> Perhaps + { use ::tengri::dsl::Val::*; todo!()//Ok(match token { #(#matchers)* _ => None }) } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index 3514696..cdfb2af 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -80,8 +80,8 @@ impl ExposeImpl { write_quote_to(out, quote! { /// Generated by [tengri_proc::expose]. impl ::tengri::dsl::FromDsl<#state> for #t { - fn try_from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { - Ok(Some(match dsl.dsl() { + fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + Ok(Some(match dsl.val() { #arms _ => { return Ok(None) } })) diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 5e11c0f..312bdd9 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -50,7 +50,7 @@ impl ToTokens for ViewDef { impl ViewDef { fn generated (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { block, .. }) = self; let self_ty = &block.self_ty; let builtins = self.builtins(); let exposed = self.exposed(); @@ -63,7 +63,7 @@ impl ViewDef { impl<'state> ::tengri::dsl::DslInto< Box + 'state> > for #self_ty { - fn try_dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) + fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) @@ -74,7 +74,7 @@ impl ViewDef { /// Expressions are handled by built-in functions /// that operate over constants and symbols. fn builtins (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { output }, ViewImpl { .. }) = self; let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? @@ -85,7 +85,7 @@ impl ViewDef { } /// Symbols are handled by user-taked functions that take no parameters but `&self`. fn exposed (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let Self(ViewMeta { .. }, ViewImpl { exposed, .. }) = self; let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { #key => return Ok(Some(self.#value().boxed())), })); diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index b8d60f4..9cd527d 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -3,7 +3,6 @@ use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; -mod tui_keys; pub use self::tui_keys::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; @@ -72,8 +71,7 @@ pub trait TuiRun + Handle + 'static> { fn run (&self, state: &Arc>) -> Usually<()>; } -impl + Handle + Send + Sync + 'static> -TuiRun for Arc> { +impl + Handle + Send + Sync + 'static> TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 9ee6be6..1d89f97 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -1,24 +1,39 @@ use crate::*; use std::time::Duration; use std::thread::{spawn, JoinHandle}; -use crossterm::event::{poll, read}; +use crossterm::event::{Event, poll, read}; #[derive(Debug, Clone)] -pub struct TuiIn( +pub struct TuiIn { /// Exit flag - pub Arc, + pub exited: Arc, /// Input event - pub crossterm::event::Event, -); - -impl Input for TuiIn { - type Event = crossterm::event::Event; - type Handled = bool; - fn event (&self) -> &crossterm::event::Event { &self.1 } - fn is_done (&self) -> bool { self.0.fetch_and(true, Relaxed) } - fn done (&self) { self.0.store(true, Relaxed); } + pub event: TuiEvent, +} +impl Input for TuiIn { + type Event = TuiEvent; + type Handled = bool; + fn event (&self) -> &TuiEvent { &self.event } + fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } + fn done (&self) { self.exited.store(true, Relaxed); } +} +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] +pub struct TuiEvent(Event); +impl Ord for TuiEvent { + fn cmp (&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf + } +} +impl From for TuiEvent { + fn from (event: Event) -> Self { + Self(event) + } +} +impl From> for TuiEvent { + fn from (x: Arc) -> Self { + TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}"))) + } } - impl TuiIn { /// Spawn the input thread. pub fn run_input + Send + Sync + 'static> ( @@ -35,8 +50,7 @@ impl TuiIn { if poll(timer).is_ok() { let event = read().unwrap(); match event { - - crossterm::event::Event::Key(KeyEvent { + Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, @@ -46,7 +60,8 @@ impl TuiIn { }, _ => { let exited = exited.clone(); - if let Err(e) = state.write().unwrap().handle(&TuiIn(exited, event)) { + let event = event.into(); + if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) { panic!("{e}") } } @@ -59,10 +74,101 @@ impl TuiIn { //#[cfg(feature = "dsl")] //impl DslInput for TuiIn { //fn matches_dsl (&self, token: &str) -> bool { - //if let Some(event) = KeyMatcher::new(token).build() { + //if let Some(event) = TuiKey::new(token).build() { //&event == self.event() //} else { //false //} //} //} + +pub struct TuiKey { + valid: bool, + key: Option, + mods: KeyModifiers, +} + +impl TuiKey { + pub fn new (token: impl AsRef) -> Self { + let token = token.as_ref(); + if token.len() < 2 { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else if token.chars().next() != Some('@') { + Self { valid: false, key: None, mods: KeyModifiers::NONE } + } else { + Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + } + } + pub fn build (self) -> Option { + if self.valid && self.key.is_some() { + Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + } else { + None + } + } + fn next (mut self, token: &str) -> Self { + let mut tokens = token.split('-').peekable(); + while let Some(token) = tokens.next() { + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + self.mods |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + self.key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + self + } + fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "tab" => Tab, + "space" => Char(' '), + "comma" => Char(','), + "period" => Char('.'), + "plus" => Char('+'), + "minus" | "dash" => Char('-'), + "equal" | "equals" => Char('='), + "underscore" => Char('_'), + "backtick" => Char('`'), + "lt" => Char('<'), + "gt" => Char('>'), + "cbopen" | "openbrace" => Char('{'), + "cbclose" | "closebrace" => Char('}'), + "bropen" | "openbracket" => Char('['), + "brclose" | "closebracket" => Char(']'), + "pgup" | "pageup" => PageUp, + "pgdn" | "pagedown" => PageDown, + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), + _ => return None, + }) + } +} diff --git a/tui/src/tui_engine/tui_keys.rs b/tui/src/tui_engine/tui_keys.rs deleted file mode 100644 index cda5899..0000000 --- a/tui/src/tui_engine/tui_keys.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -pub struct KeyMatcher { - valid: bool, - key: Option, - mods: KeyModifiers, -} - -impl KeyMatcher { - pub fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - pub fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "cbopen" | "openbrace" => Char('{'), - "cbclose" | "closebrace" => Char('}'), - "bropen" | "openbracket" => Char('['), - "brclose" | "closebracket" => Char(']'), - "pgup" | "pageup" => PageUp, - "pgdn" | "pagedown" => PageDown, - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } -} From d72a3b5b8f3f98573a194b45961b6dd5cc8f8e5f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 19 Jul 2025 19:53:02 +0300 Subject: [PATCH 122/178] fix(input): sorting out event map --- core/src/lib.rs | 72 +++----- dsl/src/dsl.rs | 85 +++------ input/src/input_dsl.rs | 293 ++++++++++--------------------- proc/src/proc_view.rs | 13 +- tui/src/tui_engine/tui_buffer.rs | 8 +- 5 files changed, 155 insertions(+), 316 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index cc04597..b06b55e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,48 +1,38 @@ pub(crate) use std::error::Error; - /// Standard result type. pub type Usually = Result>; - /// Standard optional result type. pub type Perhaps = Result, Box>; - -/// Implement the `From` trait. -#[macro_export] macro_rules! from { +/// Implement [`Debug`] in bulk. +#[macro_export] macro_rules! impl_debug(($($S:ty|$self:ident,$w:ident|$body:block)*)=>{ + $(impl std::fmt::Debug for $S { + fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body + })* }); +/// Implement [`From`] in bulk. +#[macro_export] macro_rules! from( ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { impl $(<$($lt),+>)? From<$Source> for $Target { - fn from ($state:$Source) -> Self { $cb } - } - }; -} - -pub trait Has: Send + Sync { - fn get (&self) -> &T; - fn get_mut (&mut self) -> &mut T; -} - -#[macro_export] macro_rules! has { - ($T:ty: |$self:ident : $S:ty| $x:expr) => { - impl Has<$T> for $S { - fn get (&$self) -> &$T { &$x } - fn get_mut (&mut $self) -> &mut $T { &mut $x } - } - }; -} - -pub trait MaybeHas: Send + Sync { - fn get (&self) -> Option<&T>; - fn get_mut (&mut self) -> Option<&mut T>; -} - -#[macro_export] macro_rules! maybe_has { + fn from ($state:$Source) -> Self { $cb }}}; + ($($Struct:ty { $( + $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr + );+ $(;)? })*) => { $( + $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { + fn from ($source: $From) -> Self { $expr } })+ )* }; ); +/// Type-dispatched `get` and `get_mut`. +pub trait Has: Send + Sync { fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; } +/// Implement [Has]. +#[macro_export] macro_rules! has(($T:ty: |$self:ident : $S:ty| $x:expr) => { + impl Has<$T> for $S { + fn get (&$self) -> &$T { &$x } + fn get_mut (&mut $self) -> &mut $T { &mut $x } } };); +/// Type-dispatched `get` and `get_mut` that return an [Option]-wrapped result. +pub trait MaybeHas: Send + Sync { fn get (&self) -> Option<&T>; fn get_mut (&mut self) -> Option<&mut T>; } +/// Implement [MaybeHas]. +#[macro_export] macro_rules! maybe_has( ($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => { impl MaybeHas<$T> for $S { fn get (&$self) -> Option<&$T> $x - fn get_mut (&mut $self) -> Option<&mut $T> $y - } - }; -} - + fn get_mut (&mut $self) -> Option<&mut $T> $y } };); /// May compute a `RetVal` from `Args`. pub trait Eval { /// A custom operation on [Args] that may return [Result::Err] or [Option::None]. @@ -57,15 +47,3 @@ pub trait Eval { } } } - -//impl, I, O> Eval for &S { - //fn try_eval (&self, input: I) -> Perhaps { - //(*self).try_eval(input) - //} -//} - -//impl, I: Ast, O: Dsl> Eval for S { - //fn try_eval (&self, input: I) -> Perhaps { - //Dsl::try_provide(self, input) - //} -//} diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 2f1c150..e8aad19 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -18,7 +18,7 @@ pub(crate) use self::DslError::*; #[macro_export] macro_rules! dsl_read_advance (($exp:ident, $pat:pat => $val:expr)=>{{ let (head, tail) = $exp.advance(); $exp = tail; match head { - Some($pat) => $val, _ => Err(format!("(e4) unexpected {head:?}").into()) }}}); + Some($pat) => Ok($val), _ => Err(format!("(e4) unexpected {head:?}").into()) }}}); /// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. pub trait Dsl: Clone + Debug { @@ -28,49 +28,41 @@ pub trait Dsl: Clone + Debug { type Exp: DslExp; /// Request the top-level DSL [Val]ue. /// May perform cloning or parsing. - fn val (&self) -> Val; - fn err (&self) -> Option {self.val().err()} - fn nil (&self) -> bool {self.val().nil()} - fn num (&self) -> Option {self.val().num()} - fn sym (&self) -> Option {self.val().sym()} - fn key (&self) -> Option {self.val().key()} - fn str (&self) -> Option {self.val().str()} - fn exp (&self) -> Option {self.val().exp()} - fn exp_depth (&self) -> Option {self.val().exp_depth()} - fn exp_head (&self) -> Val {self.val().exp_head()} - fn exp_tail (&self) -> Self::Exp {self.val().exp_tail()} - fn exp_each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { - todo!() - } - fn advance (&self) -> (Val, Self::Exp) { - (self.exp_head(), self.exp_tail()) - } + fn val (&self) -> Val; + fn err (&self) -> Option {self.val().err()} + fn nil (&self) -> bool {self.val().nil()} + fn num (&self) -> Option {self.val().num()} + fn sym (&self) -> Option {self.val().sym()} + fn key (&self) -> Option {self.val().key()} + fn str (&self) -> Option {self.val().str()} + fn exp (&self) -> Option {self.val().exp()} + fn depth (&self) -> Option {self.val().depth()} + fn head (&self) -> Val {self.val().head()} + fn tail (&self) -> Self::Exp {self.val().tail()} + fn advance (&self) -> (Val, Self::Exp) { (self.head(), self.tail()) } + fn each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() } + fn exp_next (&mut self) -> Option> { todo!() } } - impl<'s> Dsl for &'s str { type Str = &'s str; type Exp = &'s str; fn val (&self) -> Val { Val::Exp(0, self) } } - impl<'s> Dsl for Cst<'s> { type Str = &'s str; type Exp = Cst<'s>; fn val (&self) -> Val { Val::Exp(0, *self) } } - impl Dsl for Ast { type Str = Arc; type Exp = Ast; fn val (&self) -> Val { Val::Exp(0, self.clone()) } } - impl Dsl for Token { type Str = Str; type Exp = Exp; fn val (&self) -> Val { self.value.clone() } } - impl Dsl for Val { type Str = Str; type Exp = Exp; @@ -134,7 +126,7 @@ impl> Val { pub const fn key (&self) -> Option<&Str> {match self{Val::Key(k)=>Some(k), _=>None}} pub const fn str (&self) -> Option<&Str> {match self{Val::Str(s)=>Some(s), _=>None}} pub const fn exp (&self) -> Option<&Exp> {match self{Val::Exp(_, x)=>Some(x),_=>None}} - pub const fn exp_depth (&self) -> Option {match self{Val::Exp(d, _)=>Some(*d), _=>None}} + pub const fn depth (&self) -> Option {match self{Val::Exp(d, _)=>Some(*d), _=>None}} } /// Parsed substring with range and value. #[derive(Debug, Clone, Default, PartialEq)] @@ -363,10 +355,10 @@ pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> } start = i; length = 1; - value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) } - else if is_str_start(c) { Str(str_range(source, i, i+1)) } - else if is_sym_start(c) { Sym(str_range(source, i, i+1)) } - else if is_key_start(c) { Key(str_range(source, i, i+1)) } + value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) } + else if is_str_start(c) { Str(str_range(source, i, i+1)) } + else if is_sym_start(c) { Sym(str_range(source, i, i+1)) } + else if is_key_start(c) { Key(str_range(source, i, i+1)) } else if is_digit(c) { match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) } } else { value = Error(Unexpected(c)); break } } else if matches!(value, Str(_)) { @@ -451,33 +443,6 @@ pub const fn to_digit (c: char) -> Result { }) } -/// Implement type conversions. -macro_rules! from(($($Struct:ty { $( - $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr -);+ $(;)? })*) => { $( - $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { - fn from ($source: $From) -> Self { $expr } - })+ -)* }); - -//from! { - ////Vec> { <'s> (val: CstIter<'s>) val.collect(); } - //CstConstIter<'s> { - //<'s> (src: &'s str) Self(src); - //<'s> (iter: CstIter<'s>) iter.0; } - //CstIter<'s> { - //<'s> (src: &'s str) Self(CstConstIter(src)); - //<'s> (iter: CstConstIter<'s>) Self(iter); } - //Cst<'s> { <'s> (src: &'s str) Self(CstIter(CstConstIter(src))); } - //Vec { <'s> (val: CstIter<'s>) val.map(Into::into).collect(); } - //Token> { <'s> (token: Token>) Self { value: token.value.into(), span: token.span.into() } } - //Ast { - //<'s> (src: &'s str) Ast::from(CstIter(CstConstIter(src))); - //<'s> (cst: Cst<'s>) Ast(VecDeque::from([dsl_val(cst.val())]).into()); - //<'s> (iter: CstIter<'s>) Ast(iter.map(|x|x.value.into()).collect::>().into()); - // (token: Token) Ast(VecDeque::from([dsl_val(token.val())]).into()); } -//} - /// `T` + [Dsl] -> `Self`. pub trait FromDsl: Sized { fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; @@ -489,19 +454,19 @@ pub trait FromDsl: Sized { } } -impl, U> DslInto for U { +impl<'s, T: FromDsl, U> DslInto<'s, T> for U { fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { T::from_dsl(self, dsl) } } /// `self` + [Dsl] -> `T` -pub trait DslInto { - fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { +pub trait DslInto<'s, T> { + fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box) -> Usually { self.dsl_into(dsl)?.ok_or(err) } - fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + fn dsl_into_or_else (&'s self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { self.dsl_into(dsl)?.ok_or_else(err) } } diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 5403b66..9f6d8de 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,4 +1,8 @@ use crate::*; +/// Map of each event (e.g. key combination) to +/// all command expressions bound to it by +/// all loaded input layers. +type EventMapImpl = BTreeMap>>; /// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation @@ -8,209 +12,100 @@ use crate::*; /// When a key is pressed, the bindings for it are checked in sequence. /// When the first non-conditional or true conditional binding is executed, /// that .event()binding's value is returned. -#[derive(Debug)] pub struct EventMap( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub EventBindings -); -type EventBindings = BTreeMap>>; -/// An input binding. -#[derive(Debug, Default)] pub struct Binding { - condition: Option, - command: D, - description: Option>, - source: Option>, -} -impl Default for EventMap { - fn default () -> Self { - Self(Default::default()) +#[derive(Debug)] +pub struct EventMap(EventMapImpl); +/// Default is always empty map regardless if `E` and `C` implement [Default]. +impl Default for EventMap { fn default () -> Self { Self(Default::default()) } } +impl EventMap { + /// Create a new event map + pub fn new () -> Self { + Default::default() } -} -impl> + Clone> EventMap { - /// Create input layer collection from DSL. - pub fn new (dsl: Ast) -> Usually { - use Val::*; - let mut input_map: EventBindings = Default::default(); - match dsl.exp_head() { - Str(path) => { - let path = PathBuf::from(path.as_ref() as &str); - for (key, val) in Self::from_path(&path)?.0.into_iter() { - todo!("import {path:?} {key:?} {val:?}"); - let key: E = key.into(); - if !input_map.contains_key(&key) { - input_map.insert(key, vec![]); - } - } - }, - Sym(s) => { - let key: Arc = s.as_ref().into(); - let key: E = key.into(); - if !input_map.contains_key(&key) { - input_map.insert(key.clone(), vec![]); - } - todo!("binding {s:?} {:?}", dsl.exp_tail()); - }, - Key(k) if k.as_ref() == "if" => { - todo!("conditional binding {:?}", dsl.exp_tail()); - }, - _ => return Err(format!("invalid form in keymap: {dsl:?}").into()) - } - Ok(Self(input_map)) - } - /// Create input layer collection from string. - pub fn from_source (source: &str) -> Usually { - Self::new(Ast::from(source)) - } - /// Create input layer collection from path to text file. - pub fn from_path > (path: P) -> Usually { - if !exists(path.as_ref())? { - return Err(format!("(e5) not found: {path:?}").into()) - } - Self::from_source(read_and_leak(path)?) - } - /// Evaluate the active layers for a given state, - /// returning the command to be executed, if any. - pub fn handle (&self, state: &mut S, event: &E) -> Perhaps where - S: DslInto + DslInto, - O: Command - { - todo!(); - //let layers = self.0.as_slice(); - //for InputLayer { cond, bind } in layers.iter() { - //let mut matches = true; - //if let Some(cond) = cond { - //matches = state.dsl_into(cond, ||format!("input: no cond").into())?; - //} - //if matches - //&& let Some(exp) = bind.val().exp_head() - //&& input.dsl_into(exp, ||format!("EventMap: input.eval(binding) failed").into())? - //&& let Some(command) = state.try_dsl_into(exp)? { - //return Ok(Some(command)) - //} - //} - Ok(None) - } - /* - /// Create an input map with a single non-condal layer. - /// (Use [Default::default] to get an empty map.) - pub fn new (layer: Val) -> Self { - Self::default().layer(layer) - } - /// Add layer, return `Self`. - pub fn layer (mut self, layer: Val) -> Self { - self.add_layer(layer); self - } - /// Add condal layer, return `Self`. - pub fn layer_if (mut self, cond: Val, layer: Val) -> Self { - self.add_layer_if(Some(cond), layer); self - } - /// Add layer, return `&mut Self`. - pub fn add_layer (&mut self, layer: Val) -> &mut Self { - self.add_layer_if(None, layer.into()); self - } - /// Add condal layer, return `&mut Self`. - pub fn add_layer_if (&mut self, cond: Option>, bind: Val) -> &mut Self { - self.0.push(InputLayer { cond, bind }); + /// Add a binding to an owned event map. + pub fn def (mut self, event: E, binding: Binding) -> Self { + self.add(event, binding); self } - */ + /// Add a binding to an event map. + pub fn add (&mut self, event: E, binding: Binding) -> &mut Self { + if (!self.0.contains_key(&event)) { + self.0.insert(event.clone(), Default::default()); + } + self.0.get_mut(&event).unwrap().push(binding); + self + } + /// Return the binding(s) that correspond to an event. + pub fn query (&self, event: &E) -> Option<&[Binding]> { + self.0.get(event).map(|x|x.as_slice()) + } + /// Return the first binding that corresponds to an event, considering conditions. + pub fn dispatch (&self, event: &E) -> Option<&Binding> { + self.query(event) + .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) + .flatten() + } + /// Create event map from path to text file. + pub fn from_path > (path: P) -> Usually where E: From> { + if exists(path.as_ref())? { + Self::from_source(read_and_leak(path)?) + } else { + return Err(format!("(e5) not found: {:?}", path.as_ref()).into()) + } + } + /// Create event map from string. + pub fn from_source (source: impl AsRef) -> Usually where E: From> { + Self::from_dsl(Cst::from(source.as_ref())) + } + /// Create event map from DSL tokenizer. + pub fn from_dsl (mut dsl: impl Dsl) -> Usually where E: From> { + use Val::*; + let mut map: Self = Default::default(); + loop { + match dsl.exp_next().unwrap() { + Str(path) => { + map.0.extend(Self::from_path(PathBuf::from(path.as_ref() as &str))?.0) + }, + Exp(_, e) => match e.head() { + Key(k) if k.as_ref() == "if" => { + todo!("conditional binding {:?}", dsl.tail()); + }, + Sym(s) => { + let key: Arc = s.as_ref().into(); + let key: E = key.into(); + map.add(key, Binding::from_dsl(e)?); + todo!("binding {s:?} {:?}", dsl.tail()); + }, + _ => panic!(), + }, + _ => panic!(), + } + } + Ok(map) + } } - - - - //let mut keys = iter.unwrap(); - //let mut map = EventMap::default(); - //while let Some(token) = keys.next() { - //if let Value::Exp(_, mut exp) = token.value { - //let next = exp.next(); - //if let Some(Token { value: Value::Key(sym), .. }) = next { - //match sym { - //"layer" => { - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //map.add_layer(read_and_leak(path)?.into()); - //print!("layer:\n path: {:?}...", exp.0.0.trim()); - //println!("ok"); - //} else { - //return Err(format!("(e4) unexpected non-string {next:?}").into()) - //} - //}, - - //"layer-if" => { - //let mut cond = None; - - //if let Some(Token { value: Value::Sym(sym), .. }) = exp.next() { - //cond = Some(leak(sym)); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //}; - - //if let Some(Token { value: Value::Str(path), .. }) = exp.peek() { - //let path = base.as_ref().parent().unwrap().join(unquote(path)); - //if !std::fs::exists(&path)? { - //return Err(format!("(e5) not found: {path:?}").into()) - //} - //print!("layer-if:\n cond: {}\n path: {path:?}...", - //cond.unwrap_or_default()); - //let keys = read_and_leak(path)?.into(); - //let cond = cond.unwrap(); - //println!("ok"); - //map.add_layer_if( - //Box::new(move |state: &App|Take::take_or_fail( - //state, exp, ||"missing input layer conditional" - //)), keys - //); - //} else { - //return Err(format!("(e4) unexpected non-symbol {next:?}").into()) - //} - //}, - - //_ => return Err(format!("(e3) unexpected symbol {sym:?}").into()) - //} - //} else { - //return Err(format!("(e2) unexpected exp {:?}", next.map(|x|x.value)).into()) - //} - //} else { - //return Err(format!("(e1) unexpected token {token:?}").into()) - //} - //} - //Ok(map) - //} -//{ -//} - //fn from (source: &'s str) -> Self { - //// this should be for single layer: - //use Val::*; - //let mut layers = vec![]; - //let mut source = CstIter::from(source); - //while let Some(Exp(_, mut iter)) = source.next().map(|x|x.value) { - //let mut iter = iter.clone(); - //layers.push(match iter.next().map(|x|x.value) { - //Some(Sym(sym)) if sym.starts_with("@") => InputLayer { - //cond: None, - //bind: vec![[ - //dsl_val(source.nth(1).unwrap()), - //dsl_val(source.nth(2).unwrap()), - //]], - //}, - //Some(Str(layer)) => InputLayer { - //cond: None, - //bind: dsl_val(source.nth(1).unwrap()), - //}, - //Some(Key("if")) => InputLayer { - //cond: Some(dsl_val(source.nth(1).unwrap())), - //bind: dsl_val(source.nth(2).unwrap()), - //}, - //_ => panic!("invalid token in keymap"), - //}) - //} - //Self(layers) - //} -//} +/// An input binding. +#[derive(Debug)] +pub struct Binding { + pub command: C, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} +impl Binding { + fn from_dsl (dsl: impl Dsl) -> Usually { + let mut command: Option = None; + let mut condition: Option = None; + let mut description: Option> = None; + let mut source: Option> = None; + if let Some(command) = command { + Ok(Self { command, condition, description, source }) + } else { + Err(format!("no command in {dsl:?}").into()) + } + } +} +pub struct Condition(Boxbool + Send + Sync>); +impl_debug!(Condition |self, w| { write!(w, "*") }); fn unquote (x: &str) -> &str { let mut chars = x.chars(); diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 312bdd9..61cbe6e 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -60,10 +60,10 @@ impl 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::DslInto< + impl<'state> ::tengri::dsl::DslInto<'state, Box + 'state> > for #self_ty { - fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) + fn dsl_into (&'state self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) @@ -77,8 +77,11 @@ impl ViewDef { let Self(ViewMeta { output }, ViewImpl { .. }) = self; let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( - #builtin::from_dsl(self, expr, ||Box::new("failed to load builtin".into()))? - .boxed() + #builtin::from_dsl_or_else( + self, + &expr, + || { format!("failed to load builtin").into() } + )?.boxed() )), }); quote! { #(#builtins)* } @@ -89,7 +92,7 @@ impl ViewDef { let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { #key => return Ok(Some(self.#value().boxed())), })); - quote! { ::tengri::dsl::Val::Sym(key) => match key.as_ref() { #(#exposed)* } } + quote! { ::tengri::dsl::Val::Sym(key) => match key.as_ref() { #(#exposed)* _ => panic!() } } } } diff --git a/tui/src/tui_engine/tui_buffer.rs b/tui/src/tui_engine/tui_buffer.rs index 7fc44b9..6e3acfd 100644 --- a/tui/src/tui_engine/tui_buffer.rs +++ b/tui/src/tui_engine/tui_buffer.rs @@ -20,11 +20,9 @@ pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut C pub content: Vec } -impl std::fmt::Debug for BigBuffer { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) - } -} +impl_debug!(BigBuffer |self, f| { + write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) +}); impl BigBuffer { pub fn new (width: usize, height: usize) -> Self { From 73eb9352826e24997a478e8d90896e88a3d51203 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 20 Jul 2025 03:19:24 +0300 Subject: [PATCH 123/178] refactor(dsl): use traits instead of enums --- dsl/src/dsl.rs | 512 +++------------------------------------ dsl/src/dsl_conv.rs | 51 ++++ dsl/src/dsl_types.rs | 176 ++++++++++++++ input/src/input_dsl.rs | 30 +-- proc/src/proc_command.rs | 1 - proc/src/proc_expose.rs | 133 ++++++---- 6 files changed, 362 insertions(+), 541 deletions(-) create mode 100644 dsl/src/dsl_conv.rs create mode 100644 dsl/src/dsl_types.rs diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index e8aad19..f57c8ce 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -1,494 +1,58 @@ -#![feature(adt_const_params)] -#![feature(type_alias_impl_trait)] +//#![feature(adt_const_params)] +//#![feature(type_alias_impl_trait)] #![feature(impl_trait_in_fn_trait_return)] #![feature(const_precise_live_drops)] extern crate const_panic; -use const_panic::{concat_panic, PanicFmt}; +use const_panic::PanicFmt; +use std::fmt::Debug; pub(crate) use ::tengri_core::*; -use std::ops::Deref; pub(crate) use std::error::Error; -pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; -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 konst::string::{str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; + +mod dsl_conv; pub use self::dsl_conv::*; +mod dsl_types; pub use self::dsl_types::*; #[cfg(test)] mod dsl_test; -#[macro_export] macro_rules! dsl_read_advance (($exp:ident, $pat:pat => $val:expr)=>{{ - let (head, tail) = $exp.advance(); $exp = tail; match head { - Some($pat) => Ok($val), _ => Err(format!("(e4) unexpected {head:?}").into()) }}}); - -/// Coerce to [Val] for predefined [Self::Str] and [Self::Exp]. -pub trait Dsl: Clone + Debug { - /// The string representation for a dizzle. - type Str: DslStr; - /// The string representation for a dizzle. - type Exp: DslExp; - /// Request the top-level DSL [Val]ue. - /// May perform cloning or parsing. - fn val (&self) -> Val; - fn err (&self) -> Option {self.val().err()} - fn nil (&self) -> bool {self.val().nil()} - fn num (&self) -> Option {self.val().num()} - fn sym (&self) -> Option {self.val().sym()} - fn key (&self) -> Option {self.val().key()} - fn str (&self) -> Option {self.val().str()} - fn exp (&self) -> Option {self.val().exp()} - fn depth (&self) -> Option {self.val().depth()} - fn head (&self) -> Val {self.val().head()} - fn tail (&self) -> Self::Exp {self.val().tail()} - fn advance (&self) -> (Val, Self::Exp) { (self.head(), self.tail()) } - fn each (&self, f: impl Fn(&Self) -> Usually<()>) -> Usually<()> { todo!() } - fn exp_next (&mut self) -> Option> { todo!() } +pub trait Dsl: Debug + Send + Sync + Sized { + fn src (&self) -> &str; } + impl<'s> Dsl for &'s str { - type Str = &'s str; - type Exp = &'s str; - fn val (&self) -> Val { Val::Exp(0, self) } -} -impl<'s> Dsl for Cst<'s> { - type Str = &'s str; - type Exp = Cst<'s>; - fn val (&self) -> Val { Val::Exp(0, *self) } -} -impl Dsl for Ast { - type Str = Arc; - type Exp = Ast; - fn val (&self) -> Val { Val::Exp(0, self.clone()) } -} -impl Dsl for Token { - type Str = Str; - type Exp = Exp; - fn val (&self) -> Val { self.value.clone() } -} -impl Dsl for Val { - type Str = Str; - type Exp = Exp; - fn val (&self) -> Val { self.clone() } + fn src (&self) -> &str { self } } -/// The expression representation for a [Dsl] implementation. -/// [Cst] uses [CstIter]. [Ast] uses [VecDeque]. -pub trait DslExp: PartialEq + Clone + Debug + Dsl {} -impl DslExp for T {} -/// The string representation for a [Dsl] implementation. -/// [Cst] uses `&'s str`. [Ast] uses `Arc`. -pub trait DslStr: PartialEq + Clone + Default + Debug + AsRef + Deref { - fn as_str (&self) -> &str { self.as_ref() } - fn as_arc (&self) -> Arc { self.as_ref().into() } +impl Dsl for std::sync::Arc { + fn src (&self) -> &str { self.as_ref() } } -impl + Deref> DslStr for Str {} -/// Enumeration of values that may figure in an expression. -/// Generic over string and expression storage. -#[derive(Clone, Debug, PartialEq, Default)] -pub enum Val { - /// Empty expression - #[default] Nil, - /// Unsigned integer literal - Num(usize), - /// An identifier that starts with `.` - Sym(Str), - /// An identifier that doesn't start with `:` - Key(Str), - /// A quoted string literal - Str(Str), - /// A DSL expression. - Exp( - /// Number of unclosed parentheses. Must be 0 to be valid. - isize, - /// Expression content. - Exp - ), - /// An error. - Error(DslError), + +impl Dsl for Option { + fn src (&self) -> &str { if let Some(dsl) = self { dsl.src() } else { "" } } } -impl Copy for Val {} -impl Val { - pub fn convert_to (&self, to_str: impl Fn(&S1)->S2, to_exp: impl Fn(&E1)->E2) -> Val { - match self { - Val::Nil => Val::Nil, - Val::Num(u) => Val::Num(*u), - Val::Sym(s) => Val::Sym(to_str(s)), - Val::Key(s) => Val::Key(to_str(s)), - Val::Str(s) => Val::Str(to_str(s)), - Val::Exp(d, x) => Val::Exp(*d, to_exp(x)), - Val::Error(e) => Val::Error(*e) - } - } + +impl Dsl for &D { + fn src (&self) -> &str { (*self).src() } } -impl> Val { - pub const fn err (&self) -> Option {match self{Val::Error(e)=>Some(*e), _=>None}} - pub const fn nil (&self) -> bool {match self{Val::Nil=>true, _=>false}} - pub const fn num (&self) -> Option {match self{Val::Num(n)=>Some(*n), _=>None}} - pub const fn sym (&self) -> Option<&Str> {match self{Val::Sym(s)=>Some(s), _=>None}} - pub const fn key (&self) -> Option<&Str> {match self{Val::Key(k)=>Some(k), _=>None}} - pub const fn str (&self) -> Option<&Str> {match self{Val::Str(s)=>Some(s), _=>None}} - pub const fn exp (&self) -> Option<&Exp> {match self{Val::Exp(_, x)=>Some(x),_=>None}} - pub const fn depth (&self) -> Option {match self{Val::Exp(d, _)=>Some(*d), _=>None}} + +impl Dsl for &mut D { + fn src (&self) -> &str { (**self).src() } } -/// Parsed substring with range and value. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Token { - /// Meaning of token. - pub value: Val, - /// Reference to source text. - pub source: Str, - /// Index of 1st character of span. - pub start: usize, - /// Length of span. - pub length: usize, -} -/// Tokens are copiable where possible. -impl Copy for Token {} -/// Token methods. -impl Token { - pub const fn end (&self) -> usize { - self.start.saturating_add(self.length) } - pub const fn value (&self) - -> &Val { &self.value } - pub const fn err (&self) - -> Option { if let Val::Error(e) = self.value { Some(e) } else { None } } - pub const fn new (source: Str, start: usize, length: usize, value: Val) - -> Self { Self { value, start, length, source } } - pub const fn copy (&self) -> Self where Val: Copy, Str: Copy, Exp: Copy { - Self { value: self.value, ..*self } - } -} -/// The concrete syntax tree (CST) implements zero-copy -/// parsing of the DSL from a string reference. CST items -/// preserve info about their location in the source. -/// CST stores strings as source references and expressions as [CstIter] instances. -#[derive(Debug, Copy, Clone, PartialEq, Default)] -pub enum Cst<'s> { - #[default] __, - Source(&'s str), - Token(CstToken<'s>), - Val(CstVal<'s>), - Iter(CstIter<'s>), - ConstIter(CstConstIter<'s>), -} -impl<'s> From<&'s str> for Cst<'s> { - fn from (src: &'s str) -> Self { - Cst::Source(src) - } -} -impl<'s> From> for Cst<'s> { - fn from (token: CstToken<'s>) -> Self { - Cst::Token(token) - } -} -impl<'s> From> for Cst<'s> { - fn from (val: CstVal<'s>) -> Self { - Cst::Val(val) - } -} -impl<'s> From> for Cst<'s> { - fn from (iter: CstIter<'s>) -> Self { - Cst::Iter(iter) - } -} -impl<'s> From> for Cst<'s> { - fn from (iter: CstConstIter<'s>) -> Self { - Cst::ConstIter(iter) - } -} -impl<'s> From> for Ast { - fn from (cst: Cst<'s>) -> Self { - match cst { - Cst::Source(source) | Cst::Iter(CstIter(CstConstIter(source))) | - Cst::ConstIter(CstConstIter(source)) => - Ast::Source(source.into()), - Cst::Val(value) => - Ast::Val(value.convert_to(|s|(*s).into(), |e|Box::new((*e).into()))), - Cst::Token(Token { source, start, length, value }) => { - let source = AsRef::::as_ref(source).into(); - let value = value.convert_to(|s|(*s).into(), |e|Box::new((*e).into())); - Ast::Token(Token { source, start, length, value }) - }, - _ => unreachable!() - } - } -} -/// The abstract syntax tree (AST) can be produced from the CST -/// by cloning source slices into owned ([Arc]) string slices. -#[derive(Debug, Clone, PartialEq, Default)] -pub enum Ast { - #[default] __, - Source(Arc), - Token(Token, Box>), - Val(Val, Box>), - Exp(Arc, Ast>>>>), -} -impl<'s> From<&'s str> for Ast { - fn from (src: &'s str) -> Self { - Self::Source(src.into()) - } -} -impl<'s, Str: DslStr, Exp: DslExp + Into + 's> From> for Ast { - fn from (Token { source, start, length, value }: Token) -> Self { - let source: Arc = source.as_ref().into(); - let value = value.convert_to(|s|s.as_ref().into(), |e|Box::new(e.clone().into())); - Self::Token(Token { source, start, length, value }) - } -} -pub type CstVal<'s> = Val<&'s str, &'s str>; -pub type CstToken<'s> = Token<&'s str, &'s str>; -impl<'s> CstToken<'s> { - pub const fn slice (&self) -> &str { - str_range(self.source, self.start, self.end()) } - pub const fn slice_exp (&self) -> &str { - str_range(self.source, self.start.saturating_add(1), self.end()) } - pub const fn grow (&mut self) -> &mut Self { - let max_length = self.source.len().saturating_sub(self.start); - self.length = self.length + 1; - if self.length > max_length { self.length = max_length } - self - } - pub const fn grow_exp (&'s mut self, depth: isize, source: &'s str) -> &mut Self { - self.value = Val::Exp(depth, source); - self - } -} -//impl<'s> From<&'s str> for Ast { - //fn from (source: &'s str) -> Ast { - //let source: Arc = source.into(); - //Ast(CstIter(CstConstIter(source.as_ref())) - //.map(|token|Arc::new(Token { - //source: source.clone(), - //start: token.start, - //length: token.length, - //value: match token.value { - //Val::Nil => Val::Nil, - //Val::Num(u) => Val::Num(u), - //Val::Sym(s) => Val::Sym(s.into()), - //Val::Key(s) => Val::Key(s.into()), - //Val::Str(s) => Val::Str(s.into()), - //Val::Exp(d, x) => Val::Exp(d, x.into()), - //Val::Error(e) => Val::Error(e.into()) - //}, - //})) - //.collect::>() - //.into()) - //} -//} + +/// DSL-specific result type. +pub type DslResult = Result; + +/// DSL-specific optional result type. +pub type DslPerhaps = Result, DslError>; + /// DSL-specific error codes. -#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { - #[error("parse failed: not implemented")] - Unimplemented, - #[error("parse failed: empty")] - Empty, - #[error("parse failed: incomplete")] - Incomplete, - #[error("parse failed: unexpected character '{0}'")] - Unexpected(char), - #[error("parse failed: error #{0}")] - Code(u8), - #[error("end reached")] - End -} -/// Provides native [Iterator] API over [CstConstIter], emitting [Cst] items. -/// -/// [Cst::next] returns just the [Cst] and mutates `self`, -/// instead of returning an updated version of the struct as [CstConstIter::next] does. -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstIter<'s>(pub CstConstIter<'s>); -impl<'s> CstIter<'s> { - pub const fn new (source: &'s str) -> Self { Self(CstConstIter::new(source)) } -} -impl<'s> Iterator for CstIter<'s> { - type Item = CstToken<'s>; - fn next (&mut self) -> Option { - match self.0.advance() { - Some((item, rest)) => { self.0 = rest; Some(item.into()) }, - None => None, - } - } -} -/// Holds a reference to the source text. -/// [CstConstIter::next] emits subsequent pairs of: -/// * a [Cst] and -/// * the source text remaining -/// * [ ] TODO: maybe [CstConstIter::next] should wrap the remaining source in `Self` ? -#[derive(Copy, Clone, Debug, Default, PartialEq)] -pub struct CstConstIter<'s>(pub &'s str); -impl<'s> From <&'s str> for CstConstIter<'s> { - fn from (src: &'s str) -> Self { Self(src) } -} -impl<'s> Iterator for CstConstIter<'s> { - type Item = CstToken<'s>; - fn next (&mut self) -> Option> { self.advance().map(|x|x.0) } -} -impl<'s> ConstIntoIter for CstConstIter<'s> { - type Kind = IsIteratorKind; - type Item = Cst<'s>; - type IntoIter = Self; -} -impl<'s> CstConstIter<'s> { - pub const fn new (source: &'s str) -> Self { Self(source) } - pub const fn chomp (&self, index: usize) -> Self { Self(split_at(self.0, index).1) } - pub const fn advance (&mut self) -> Option<(CstToken<'s>, Self)> { - match peek(Val::Nil, self.0) { - Token { value: Val::Nil, .. } => None, - token => { - let end = self.chomp(token.end()); - Some((token.copy(), end)) - }, - } - } -} - -pub const fn peek <'s> (mut value: CstVal<'s>, source: &'s str) -> CstToken<'s> { - use Val::*; - let mut start = 0; - let mut length = 0; - let mut source = source; - loop { - if let Some(((i, c), next)) = char_indices(source).next() { - if matches!(value, Error(_)) { - break - } else if matches!(value, Nil) { - if is_whitespace(c) { - length += 1; - continue - } - start = i; - length = 1; - value = if is_exp_start(c) { Exp(1, str_range(source, i, i+1)) } - else if is_str_start(c) { Str(str_range(source, i, i+1)) } - else if is_sym_start(c) { Sym(str_range(source, i, i+1)) } - else if is_key_start(c) { Key(str_range(source, i, i+1)) } - else if is_digit(c) { match to_digit(c) { Ok(c) => Num(c), Err(e) => Error(e) } } - else { value = Error(Unexpected(c)); break } - } else if matches!(value, Str(_)) { - if is_str_end(c) { - break - } else { - value = Str(str_range(source, start, start + length + 1)); - } - } else if matches!(value, Sym(_)) { - if is_sym_end(c) { - break - } else if is_sym_char(c) { - value = Sym(str_range(source, start, start + length + 1)); - } else { - value = Error(Unexpected(c)); - } - } else if matches!(value, Key(_)) { - if is_key_end(c) { - break - } - length += 1; - if is_key_char(c) { - value = Key(str_range(source, start, start + length + 1)); - } else { - value = Error(Unexpected(c)); - } - } else if let Exp(depth, exp) = value { - if depth == 0 { - value = Exp(0, str_range(source, start, start + length)); - break - } - length += 1; - value = Exp( - if c == ')' { depth-1 } else if c == '(' { depth+1 } else { depth }, - str_range(source, start, start + length) - ); - } else if let Num(m) = value { - if is_num_end(c) { - break - } - length += 1; - match to_digit(c) { - Ok(n) => { value = Num(n+10*m); }, - Err(e) => { value = Error(e); } - } - } - } else { - break - } - } - return Token { value, source, start, length } -} -const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } -const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } -const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } -const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } -const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } -const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -const fn is_str_start (c: char) -> bool { matches!(c, '"') } -const fn is_str_end (c: char) -> bool { matches!(c, '"') } -const fn is_exp_start (c: char) -> bool { matches!(c, '(') } -pub const fn to_number (digits: &str) -> Result { - let mut iter = char_indices(digits); - let mut value = 0; - while let Some(((_, c), next)) = iter.next() { - match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Err(e) => return Err(e), - } - iter = next; - } - Ok(value) -} -pub const fn to_digit (c: char) -> Result { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Err(Unexpected(c)) - }) -} - -/// `T` + [Dsl] -> `Self`. -pub trait FromDsl: Sized { - fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; - fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box) -> Usually { - Self::from_dsl(state, dsl)?.ok_or(err) - } - fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - Self::from_dsl(state, dsl)?.ok_or_else(err) - } -} - -impl<'s, T: FromDsl, U> DslInto<'s, T> for U { - fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { - T::from_dsl(self, dsl) - } -} - -/// `self` + [Dsl] -> `T` -pub trait DslInto<'s, T> { - fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box) -> Usually { - self.dsl_into(dsl)?.ok_or(err) - } - fn dsl_into_or_else (&'s self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - self.dsl_into(dsl)?.ok_or_else(err) - } -} - -/// `self` + `T` + -> [Dsl] -pub trait IntoDsl { - fn into_dsl (&self, state: &T) -> Perhaps; - fn into_dsl_or (&self, state: &T, err: Box) -> Usually { - self.into_dsl(state)?.ok_or(err) - } - fn into_dsl_or_else (&self, state: &T, err: impl Fn()->Box) -> Usually { - self.into_dsl(state)?.ok_or_else(err) - } -} - -/// `self` + `T` -> [Dsl] -pub trait DslFrom { - fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_from_or (&self, dsl: &impl Dsl, err: Box) -> Usually { - self.dsl_from(dsl)?.ok_or(err) - } - fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - self.dsl_from(dsl)?.ok_or_else(err) - } +#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] +pub enum DslError { + #[error("parse failed: not implemented")] Unimplemented, + #[error("parse failed: empty")] Empty, + #[error("parse failed: incomplete")] Incomplete, + #[error("parse failed: unexpected character '{0}'")] Unexpected(char), + #[error("parse failed: error #{0}")] Code(u8), + #[error("end reached")] End } diff --git a/dsl/src/dsl_conv.rs b/dsl/src/dsl_conv.rs new file mode 100644 index 0000000..65f05d6 --- /dev/null +++ b/dsl/src/dsl_conv.rs @@ -0,0 +1,51 @@ +use crate::*; + +/// `T` + [Dsl] -> `Self`. +pub trait FromDsl: Sized { + fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; + fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or(err) + } + fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + Self::from_dsl(state, dsl)?.ok_or_else(err) + } +} + +impl<'s, T: FromDsl, U> DslInto<'s, T> for U { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + T::from_dsl(self, dsl) + } +} + +/// `self` + [Dsl] -> `T` +pub trait DslInto<'s, T> { + fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_into(dsl)?.ok_or(err) + } + fn dsl_into_or_else (&'s self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_into(dsl)?.ok_or_else(err) + } +} + +/// `self` + `T` + -> [Dsl] +pub trait IntoDsl { + fn into_dsl (&self, state: &T) -> Perhaps; + fn into_dsl_or (&self, state: &T, err: Box) -> Usually { + self.into_dsl(state)?.ok_or(err) + } + fn into_dsl_or_else (&self, state: &T, err: impl Fn()->Box) -> Usually { + self.into_dsl(state)?.ok_or_else(err) + } +} + +/// `self` + `T` -> [Dsl] +pub trait DslFrom { + fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_from_or (&self, dsl: &impl Dsl, err: Box) -> Usually { + self.dsl_from(dsl)?.ok_or(err) + } + fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + self.dsl_from(dsl)?.ok_or_else(err) + } +} diff --git a/dsl/src/dsl_types.rs b/dsl/src/dsl_types.rs new file mode 100644 index 0000000..0a5402d --- /dev/null +++ b/dsl/src/dsl_types.rs @@ -0,0 +1,176 @@ +use crate::*; + +macro_rules! iter_chars(($source:expr => |$i:ident, $c:ident|$val:expr)=>{ + while let Some((($i, $c), next)) = char_indices($source).next() { + $source = next.as_str(); $val } }); + +macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ + /// Find a slice corrensponding to a syntax token. + const fn $peek (source: &str) -> DslPerhaps<&str> { + match $seek(source) { + Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), + Ok(None) => Ok(None), + Err(e) => Err(e) } } + /// Find a start and length corresponding to a syntax token. + const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { + match $seek_start(source) { + Ok(Some(start)) => match $seek_length(str_range(source, start, source.len() - start)) { + Ok(Some(length)) => Ok(Some((start, length))), + Ok(None) => Ok(None), + Err(e) => Err(e), + }, + Ok(None) => Ok(None), + Err(e) => Err(e) } } }); + +const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } + +pub trait DslExp: Dsl { + fn exp (&self) -> DslPerhaps { + todo!(); + Ok(Some(self)) + } + fn exp_head (&self) -> DslPerhaps { + todo!(); + Ok(Some(self)) + } + fn exp_tail (&self) -> DslPerhaps { + todo!(); + Ok(Some(self)) + } + fn exp_next (&mut self) -> DslPerhaps { + todo!(); + Ok(Some(self)) + } +} +impl DslExp for D {} +def_peek_seek!(exp_peek, exp_seek, exp_seek_start, exp_seek_length); +const fn is_exp_start (c: char) -> bool { matches!(c, '(') } +const fn is_exp_end (c: char) -> bool { matches!(c, ')') } +const fn exp_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_exp_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn exp_seek_length (mut source: &str) -> DslPerhaps { + let mut depth = 0; + iter_chars!(source => |i, c| if is_exp_start(c) { + depth += 1; + } else if is_exp_end(c) { + if depth == 0 { + return Err(Unexpected(c)) + } else if depth == 1 { + return Ok(Some(i)) + } else { + depth -= 1; + } + }); + Ok(None) +} + +pub trait DslSym: Dsl { fn sym (&self) -> DslPerhaps<&str> { sym_peek(self.src()) } } +impl DslSym for D {} +def_peek_seek!(sym_peek, sym_seek, sym_seek_start, sym_seek_length); +const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } +const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } +const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +const fn sym_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_sym_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn sym_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_sym_end(c) { + return Ok(Some(i)) + } else if !is_sym_char(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} + +pub trait DslKey: Dsl { fn key (&self) -> DslPerhaps<&str> { key_peek(self.src()) } } +impl DslKey for D {} +def_peek_seek!(key_peek, key_seek, key_seek_start, key_seek_length); +const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } +const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } +const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +const fn key_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_key_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn key_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_key_end(c) { + return Ok(Some(i)) + } else if !is_key_char(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} + +pub trait DslText: Dsl { fn text (&self) -> DslPerhaps<&str> { text_peek(self.src()) } } +impl DslText for D {} +def_peek_seek!(text_peek, text_seek, text_seek_start, text_seek_length); +const fn is_text_start (c: char) -> bool { matches!(c, '"') } +const fn is_text_end (c: char) -> bool { matches!(c, '"') } +const fn text_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_text_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn text_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_text_end(c) { return Ok(Some(i)) }); + Ok(None) +} + +pub trait DslNum: Dsl { fn num (&self) -> DslPerhaps<&str> { num_peek(self.src()) } } +impl DslNum for D {} +def_peek_seek!(num_peek, num_seek, num_seek_start, num_seek_length); +const fn num_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_digit(c) { + return Ok(Some(i)); + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn num_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_num_end(c) { + return Ok(Some(i)) + } else if !is_digit(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } +const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn to_number (digits: &str) -> Result { + let mut iter = char_indices(digits); + let mut value = 0; + while let Some(((_, c), next)) = iter.next() { + match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Err(e) => return Err(e), + } + iter = next; + } + Ok(value) +} +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Err(Unexpected(c)) + }) +} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 9f6d8de..0bec9a8 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -54,30 +54,20 @@ impl EventMap { } /// Create event map from string. pub fn from_source (source: impl AsRef) -> Usually where E: From> { - Self::from_dsl(Cst::from(source.as_ref())) + Self::from_dsl(source.as_ref()) } /// Create event map from DSL tokenizer. pub fn from_dsl (mut dsl: impl Dsl) -> Usually where E: From> { - use Val::*; let mut map: Self = Default::default(); - loop { - match dsl.exp_next().unwrap() { - Str(path) => { - map.0.extend(Self::from_path(PathBuf::from(path.as_ref() as &str))?.0) - }, - Exp(_, e) => match e.head() { - Key(k) if k.as_ref() == "if" => { - todo!("conditional binding {:?}", dsl.tail()); - }, - Sym(s) => { - let key: Arc = s.as_ref().into(); - let key: E = key.into(); - map.add(key, Binding::from_dsl(e)?); - todo!("binding {s:?} {:?}", dsl.tail()); - }, - _ => panic!(), - }, - _ => panic!(), + while let Some(dsl) = dsl.exp_next()? { + if let Some(path) = dsl.text()? { + map.0.extend(Self::from_path(PathBuf::from(path.as_ref() as &str))?.0) + } else if dsl.exp_head()?.key()? == Some("if") { + todo!() + //map.add(sym.into(), Binding::from_dsl(dsl.exp_tail())?); + } else if let Some(sym) = dsl.exp_head()?.sym()? { + todo!() + //map.add(sym.into(), Binding::from_dsl(dsl.exp_tail())?); } } Ok(map) diff --git a/proc/src/proc_command.rs b/proc/src/proc_command.rs index d0db6e3..48ae0fe 100644 --- a/proc/src/proc_command.rs +++ b/proc/src/proc_command.rs @@ -153,7 +153,6 @@ impl ToTokens for CommandDef { fn from_dsl (state: &#state, value: &impl ::tengri::dsl::Dsl) -> Perhaps { - use ::tengri::dsl::Val::*; todo!()//Ok(match token { #(#matchers)* _ => None }) } } diff --git a/proc/src/proc_expose.rs b/proc/src/proc_expose.rs index cdfb2af..23bf062 100644 --- a/proc/src/proc_expose.rs +++ b/proc/src/proc_expose.rs @@ -67,63 +67,104 @@ impl ToTokens for ExposeImpl { } } +fn is_num (t: &impl AsRef) -> bool { + return matches!(t.as_ref(), + | "u8" | "u16" | "u32" | "u64" | "usize" + | "i8" | "i16" | "i32" | "i64" | "isize") } + impl ExposeImpl { fn expose_variants ( &self, out: &mut TokenStream2, t: &ExposeType, variants: &BTreeMap ) { let Self(ItemImpl { self_ty: state, .. }, ..) = self; - let arms = variants.iter().map(|(key, value)|{ + let type_is = format!("{}", quote! { #t }); + let variants = variants.iter().map(|(key, value)|{ let key = LitStr::new(&key, Span::call_site()); - quote! { #key => state.#value(), } + quote! { Some(#key) => Ok(Some(state.#value())), } }); - let arms = Self::with_predefined(t, quote! { #(#arms)* }); - write_quote_to(out, quote! { - /// Generated by [tengri_proc::expose]. - impl ::tengri::dsl::FromDsl<#state> for #t { - fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { - Ok(Some(match dsl.val() { - #arms - _ => { return Ok(None) } - })) + write_quote_to(out, if &type_is == "bool" { + quote! { + /// Generated by [tengri_proc::expose]. + impl ::tengri::dsl::FromDsl<#state> for #t { + fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + match dsl.key()? { + Some("true") => Ok(Some(true)), + Some("false") => Ok(Some(false)), + _ => match dsl.sym()? { #(#variants)* _ => Ok(None) } + } + } } } - }); - } - fn with_predefined (t: &ExposeType, variants: impl ToTokens) -> impl ToTokens { - let formatted_type = format!("{}", quote! { #t }); - if &formatted_type == "bool" { - return quote! { - ::tengri::dsl::Val::Sym(s) => match s.as_ref() { - ":true" => true, - ":false" => false, - #variants - _ => { return Ok(None) } - }, + } else if is_num(&type_is) { + quote! { + /// Generated by [tengri_proc::expose]. + impl ::tengri::dsl::FromDsl<#state> for #t { + fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + match dsl.num()? { + Some(n) => Ok(Some(n.parse::<#t>()?)), + _ => match dsl.sym()? { #(#variants)* _ => Ok(None) } + } + } + } } - } - if matches!(formatted_type.as_str(), - "u8" | "u16" | "u32" | "u64" | "usize" | - "i8" | "i16" | "i32" | "i64" | "isize") - { - let num_err = LitStr::new( - &format!("{{n}}: failed to convert to {formatted_type}"), - Span::call_site() - ); - return quote! { - ::tengri::dsl::Val::Num(n) => TryInto::<#t>::try_into(n) - .unwrap_or_else(|_|panic!(#num_err)), - ::tengri::dsl::Val::Sym(s) => match s.as_ref() { - #variants - _ => { return Ok(None) } - }, + } else { + quote! { + /// Generated by [tengri_proc::expose]. + impl ::tengri::dsl::FromDsl<#state> for #t { + fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + match dsl.sym()? { #(#variants)* _ => Ok(None) } + } + } } - } - return quote! { - ::tengri::dsl::Val::Sym(s) => match s.as_ref() { - #variants - _ => { return Ok(None) } - }, - } + }) + //let arms = variants.iter().map(|(key, value)|{ + //let key = LitStr::new(&key, Span::call_site()); + //quote! { #key => state.#value(), } + //}); + //if &type_is == "bool" { + //return quote! { + //::tengri::dsl::Val::Sym(s) => match s.as_ref() { + //":true" => true, + //":false" => false, + //#variants + //_ => { return Ok(None) } + //}, + //} + //} + //if matches!(type_is.as_str(), + //"u8" | "u16" | "u32" | "u64" | "usize" | + //"i8" | "i16" | "i32" | "i64" | "isize") + //{ + //let num_err = LitStr::new( + //&format!("{{n}}: failed to convert to {type_is}"), + //Span::call_site() + //); + //return quote! { + //::tengri::dsl::Val::Num(n) => TryInto::<#t>::try_into(n) + //.unwrap_or_else(|_|panic!(#num_err)), + //::tengri::dsl::Val::Sym(s) => match s.as_ref() { + //#variants + //_ => { return Ok(None) } + //}, + //} + //} + //let arms = quote! { + //::tengri::dsl::Val::Sym(s) => match s.as_ref() { + //#variants + //_ => { return Ok(None) } + //}, + //}; + //let builtins = + //write_quote_to(out, quote! { + ///// Generated by [tengri_proc::expose]. + //impl ::tengri::dsl::FromDsl<#state> for #t { + //fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps { + //$builtins + //$defined + //Ok(None) + //} + //} + //}); } } From 360b404b69abb1ec4e0e9959667e08d4b99c6131 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 20 Jul 2025 04:49:10 +0300 Subject: [PATCH 124/178] fix(proc): update macros --- dsl/src/dsl.rs | 5 +- dsl/src/dsl_conv.rs | 34 +++++++------ proc/src/proc_view.rs | 108 +++++++++++++++++++++++------------------- 3 files changed, 82 insertions(+), 65 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index f57c8ce..530e4f5 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -7,7 +7,6 @@ use const_panic::PanicFmt; use std::fmt::Debug; pub(crate) use ::tengri_core::*; pub(crate) use std::error::Error; -pub(crate) use std::sync::Arc; pub(crate) use konst::string::{str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use self::DslError::*; @@ -24,6 +23,10 @@ impl<'s> Dsl for &'s str { fn src (&self) -> &str { self } } +impl Dsl for String { + fn src (&self) -> &str { self.as_str() } +} + impl Dsl for std::sync::Arc { fn src (&self) -> &str { self.as_ref() } } diff --git a/dsl/src/dsl_conv.rs b/dsl/src/dsl_conv.rs index 65f05d6..0600679 100644 --- a/dsl/src/dsl_conv.rs +++ b/dsl/src/dsl_conv.rs @@ -11,12 +11,6 @@ pub trait FromDsl: Sized { } } -impl<'s, T: FromDsl, U> DslInto<'s, T> for U { - fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { - T::from_dsl(self, dsl) - } -} - /// `self` + [Dsl] -> `T` pub trait DslInto<'s, T> { fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; @@ -28,6 +22,17 @@ pub trait DslInto<'s, T> { } } +/// `self` + `T` -> [Dsl] +pub trait DslFrom { + fn dsl_from (&self, dsl: &T) -> Perhaps; + fn dsl_from_or (&self, dsl: &T, err: Box) -> Usually { + self.dsl_from(dsl)?.ok_or(err) + } + fn dsl_from_or_else (&self, dsl: &T, err: impl Fn()->Box) -> Usually { + self.dsl_from(dsl)?.ok_or_else(err) + } +} + /// `self` + `T` + -> [Dsl] pub trait IntoDsl { fn into_dsl (&self, state: &T) -> Perhaps; @@ -39,13 +44,14 @@ pub trait IntoDsl { } } -/// `self` + `T` -> [Dsl] -pub trait DslFrom { - fn dsl_from (&self, dsl: &impl Dsl) -> Perhaps; - fn dsl_from_or (&self, dsl: &impl Dsl, err: Box) -> Usually { - self.dsl_from(dsl)?.ok_or(err) - } - fn dsl_from_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { - self.dsl_from(dsl)?.ok_or_else(err) +impl<'s, T: FromDsl, U> DslInto<'s, T> for U { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + T::from_dsl(self, dsl) + } +} + +impl, U> IntoDsl for U { + fn into_dsl (&self, state: &T) -> Perhaps { + T::dsl_from(state, self) } } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 61cbe6e..bd44c96 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -50,10 +50,28 @@ impl ToTokens for ViewDef { impl ViewDef { fn generated (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { block, .. }) = self; - let self_ty = &block.self_ty; - let builtins = self.builtins(); - let exposed = self.exposed(); + let Self(ViewMeta { output }, ViewImpl { block, exposed }) = self; + let self_ty = &block.self_ty; + // Expressions are handled by built-in functions that operate over constants and symbols. + let builtins = builtins_with_boxes_output(quote! { #output }) + .map(|(builtin, builtin_ty)|match builtin { + Single(name) => quote! { + if dsl.exp_head()?.key()? == Some(#name) { + return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl, + ||format!("failed to load builtin").into())?.boxed())) + } + }, + Prefix(name) => quote! { + if let Some(key) = dsl.exp_head()?.key()? && key.starts_with(#name) { + return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl, + ||format!("failed to load builtin").into())?.boxed())) + } + }, + }); + // Symbols are handled by user-provided functions that take no parameters but `&self`. + let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { + #key => return Ok(Some(self.#value().boxed())), + })); quote! { /// Generated by [tengri_proc]. /// @@ -66,63 +84,53 @@ impl ViewDef { fn dsl_into (&'state self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { - Ok(match dsl.val() { #builtins #exposed _ => return Ok(None) }) + #(#builtins)* + if let Some(sym) = dsl.sym()? { + match sym { + #(#exposed)* + _ => return Err(format!("unknown symbol {sym}").into()) + } + } + Ok(None) } } } } - /// Expressions are handled by built-in functions - /// that operate over constants and symbols. - fn builtins (&self) -> impl ToTokens { - let Self(ViewMeta { output }, ViewImpl { .. }) = self; - let builtins = builtins_with_boxes_output(quote! { #output }).map(|builtin|quote! { - ::tengri::dsl::Val::Exp(_, expr) => return Ok(Some( - #builtin::from_dsl_or_else( - self, - &expr, - || { format!("failed to load builtin").into() } - )?.boxed() - )), - }); - quote! { #(#builtins)* } - } - /// Symbols are handled by user-taked functions that take no parameters but `&self`. - fn exposed (&self) -> impl ToTokens { - let Self(ViewMeta { .. }, ViewImpl { exposed, .. }) = self; - let exposed = exposed.iter().map(|(key, value)|write_quote(quote! { - #key => return Ok(Some(self.#value().boxed())), - })); - quote! { ::tengri::dsl::Val::Sym(key) => match key.as_ref() { #(#exposed)* _ => panic!() } } - } } -fn _builtins_with_holes () -> impl Iterator { +enum Builtin { + Single(TokenStream2), + Prefix(TokenStream2), +} +use Builtin::*; + +fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator { + [ + (Single(quote!("when")), quote!(When::< #c >)), + (Single(quote!("either")), quote!(Either::< #c, #c>)), + (Prefix(quote!("align/")), quote!(Align::< #c >)), + (Prefix(quote!("bsp/")), quote!(Bsp::< #c, #c>)), + (Prefix(quote!("fill/")), quote!(Fill::< #c >)), + (Prefix(quote!("fixed/")), quote!(Fixed::<#n, #c >)), + (Prefix(quote!("min/")), quote!(Min::<#n, #c >)), + (Prefix(quote!("max/")), quote!(Max::<#n, #c >)), + (Prefix(quote!("shrink/")), quote!(Shrink::<#n, #c >)), + (Prefix(quote!("expand/")), quote!(Expand::<#n, #c >)), + (Prefix(quote!("push/")), quote!(Push::<#n, #c >)), + (Prefix(quote!("pull/")), quote!(Pull::<#n, #c >)), + (Prefix(quote!("margin/")), quote!(Margin::<#n, #c >)), + (Prefix(quote!("padding/")), quote!(Padding::<#n, #c >)), + ].into_iter() +} + +fn _builtins_with_holes () -> impl Iterator { builtins_with(quote! { _ }, quote! { _ }) } -fn _builtins_with_boxes () -> impl Iterator { +fn _builtins_with_boxes () -> impl Iterator { builtins_with(quote! { _ }, quote! { Box> }) } -fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator { +fn builtins_with_boxes_output (o: TokenStream2) -> impl Iterator { builtins_with(quote! { _ }, quote! { Box> }) } - -fn builtins_with (n: TokenStream2, c: TokenStream2) -> impl Iterator { - [ - quote! { When::< #c > }, - quote! { Either::< #c, #c> }, - quote! { Align::< #c > }, - quote! { Bsp::< #c, #c> }, - quote! { Fill::< #c > }, - quote! { Fixed::<#n, #c > }, - quote! { Min::<#n, #c > }, - quote! { Max::<#n, #c > }, - quote! { Shrink::<#n, #c > }, - quote! { Expand::<#n, #c > }, - quote! { Push::<#n, #c > }, - quote! { Pull::<#n, #c > }, - quote! { Margin::<#n, #c > }, - quote! { Padding::<#n, #c > }, - ].into_iter() -} From 8cbd7dd8e8c71fbca146aa61640e16794ecba894 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 29 Jul 2025 14:17:01 +0300 Subject: [PATCH 125/178] core, input: add flex_trait --- core/src/core_macros.rs | 74 ++++++++++++++++++++++++++++++++++++ core/src/lib.rs | 27 +------------ dsl/src/dsl.rs | 13 +++++-- dsl/src/dsl_types.rs | 63 ++++++++++++++++++++++--------- input/src/input_dsl.rs | 39 +++++++++++++------ input/src/input_handle.rs | 79 +++++++++++++++++++++------------------ proc/src/proc_view.rs | 4 +- 7 files changed, 201 insertions(+), 98 deletions(-) create mode 100644 core/src/core_macros.rs diff --git a/core/src/core_macros.rs b/core/src/core_macros.rs new file mode 100644 index 0000000..b819764 --- /dev/null +++ b/core/src/core_macros.rs @@ -0,0 +1,74 @@ +use crate::*; + +/// Define and reexport submodules. +#[macro_export] macro_rules! modules( + ($($($feat:literal?)? $name:ident),* $(,)?) => { $( + $(#[cfg(feature=$feat)])? mod $name; + $(#[cfg(feature=$feat)])? pub use self::$name::*; + )* }; + ($($($feat:literal?)? $name:ident $body:block),* $(,)?) => { $( + $(#[cfg(feature=$feat)])? mod $name $body + $(#[cfg(feature=$feat)])? pub use self::$name::*; + )* }; +); + +/// Define a trait for various wrapper types. */ +#[macro_export] macro_rules! flex_trait_mut ( + ($Trait:ident $(<$($A:ident:$T:ident),+>)? { + $(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)* + })=>{ + pub trait $Trait $(<$($A: $T),+>)? { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { + if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) } + })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> { + $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* + } + }; +); + +/// Implement [`Debug`] in bulk. +#[macro_export] macro_rules! impl_debug(($($S:ty|$self:ident,$w:ident|$body:block)*)=>{ + $(impl std::fmt::Debug for $S { + fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body + })* }); + +/// Implement [`From`] in bulk. +#[macro_export] macro_rules! from( + ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { + impl $(<$($lt),+>)? From<$Source> for $Target { + fn from ($state:$Source) -> Self { $cb }}}; + ($($Struct:ty { $( + $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr + );+ $(;)? })*) => { $( + $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { + fn from ($source: $From) -> Self { $expr } })+ )* }; ); + +/// Implement [Has]. +#[macro_export] macro_rules! has(($T:ty: |$self:ident : $S:ty| $x:expr) => { + impl Has<$T> for $S { + fn get (&$self) -> &$T { &$x } + fn get_mut (&mut $self) -> &mut $T { &mut $x } } };); + +/// Implement [MaybeHas]. +#[macro_export] macro_rules! maybe_has( + ($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => { + impl MaybeHas<$T> for $S { + fn get (&$self) -> Option<&$T> $x + fn get_mut (&mut $self) -> Option<&mut $T> $y } };); diff --git a/core/src/lib.rs b/core/src/lib.rs index b06b55e..0c63c05 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,38 +1,13 @@ +mod core_macros; pub(crate) use std::error::Error; /// Standard result type. pub type Usually = Result>; /// Standard optional result type. pub type Perhaps = Result, Box>; -/// Implement [`Debug`] in bulk. -#[macro_export] macro_rules! impl_debug(($($S:ty|$self:ident,$w:ident|$body:block)*)=>{ - $(impl std::fmt::Debug for $S { - fn fmt (&$self, $w: &mut std::fmt::Formatter) -> std::fmt::Result $body - })* }); -/// Implement [`From`] in bulk. -#[macro_export] macro_rules! from( - ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { - impl $(<$($lt),+>)? From<$Source> for $Target { - fn from ($state:$Source) -> Self { $cb }}}; - ($($Struct:ty { $( - $(<$($l:lifetime),* $($T:ident$(:$U:ident)?),*>)? ($source:ident: $From:ty) $expr:expr - );+ $(;)? })*) => { $( - $(impl $(<$($l),* $($T$(:$U)?),*>)? From<$From> for $Struct { - fn from ($source: $From) -> Self { $expr } })+ )* }; ); /// Type-dispatched `get` and `get_mut`. pub trait Has: Send + Sync { fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; } -/// Implement [Has]. -#[macro_export] macro_rules! has(($T:ty: |$self:ident : $S:ty| $x:expr) => { - impl Has<$T> for $S { - fn get (&$self) -> &$T { &$x } - fn get_mut (&mut $self) -> &mut $T { &mut $x } } };); /// Type-dispatched `get` and `get_mut` that return an [Option]-wrapped result. pub trait MaybeHas: Send + Sync { fn get (&self) -> Option<&T>; fn get_mut (&mut self) -> Option<&mut T>; } -/// Implement [MaybeHas]. -#[macro_export] macro_rules! maybe_has( - ($T:ty: |$self:ident : $S:ty| $x:block; $y:block $(;)?) => { - impl MaybeHas<$T> for $S { - fn get (&$self) -> Option<&$T> $x - fn get_mut (&mut $self) -> Option<&mut $T> $y } };); /// May compute a `RetVal` from `Args`. pub trait Eval { /// A custom operation on [Args] that may return [Result::Err] or [Option::None]. diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 530e4f5..8f83589 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -1,5 +1,6 @@ //#![feature(adt_const_params)] //#![feature(type_alias_impl_trait)] +#![feature(if_let_guard)] #![feature(impl_trait_in_fn_trait_return)] #![feature(const_precise_live_drops)] extern crate const_panic; @@ -31,10 +32,6 @@ impl Dsl for std::sync::Arc { fn src (&self) -> &str { self.as_ref() } } -impl Dsl for Option { - fn src (&self) -> &str { if let Some(dsl) = self { dsl.src() } else { "" } } -} - impl Dsl for &D { fn src (&self) -> &str { (*self).src() } } @@ -43,6 +40,14 @@ impl Dsl for &mut D { fn src (&self) -> &str { (**self).src() } } +impl Dsl for std::sync::Arc { + fn src (&self) -> &str { (*self).src() } +} + +impl Dsl for Option { + fn src (&self) -> &str { if let Some(dsl) = self { dsl.src() } else { "" } } +} + /// DSL-specific result type. pub type DslResult = Result; diff --git a/dsl/src/dsl_types.rs b/dsl/src/dsl_types.rs index 0a5402d..c9d69a1 100644 --- a/dsl/src/dsl_types.rs +++ b/dsl/src/dsl_types.rs @@ -1,9 +1,7 @@ use crate::*; - macro_rules! iter_chars(($source:expr => |$i:ident, $c:ident|$val:expr)=>{ while let Some((($i, $c), next)) = char_indices($source).next() { $source = next.as_str(); $val } }); - macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ /// Find a slice corrensponding to a syntax token. const fn $peek (source: &str) -> DslPerhaps<&str> { @@ -22,27 +20,56 @@ macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_l Ok(None) => Ok(None), Err(e) => Err(e) } } }); -const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } +pub const fn peek (mut src: &str) -> DslPerhaps<&str> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_peek(src) => exp, + _ if let Ok(Some(sym)) = sym_peek(src) => sym, + _ if let Ok(Some(key)) = key_peek(src) => key, + _ if let Ok(Some(num)) = num_peek(src) => num, + _ if let Ok(Some(text)) = text_peek(src) => text, + _ => { + iter_chars!(src => |_i, c| if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} -pub trait DslExp: Dsl { - fn exp (&self) -> DslPerhaps { - todo!(); - Ok(Some(self)) +pub const fn seek (mut src: &str) -> DslPerhaps<(usize, usize)> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_seek(src) => exp, + _ if let Ok(Some(sym)) = sym_seek(src) => sym, + _ if let Ok(Some(key)) = key_seek(src) => key, + _ if let Ok(Some(num)) = num_seek(src) => num, + _ if let Ok(Some(text)) = text_seek(src) => text, + _ => { + iter_chars!(src => |_i, c| if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} + +const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } +pub trait DslExp<'s>: Dsl + From<&'s str> { + fn exp (&'s self) -> DslPerhaps { + Ok(exp_peek(self.src())?.map(Into::into)) } - fn exp_head (&self) -> DslPerhaps { - todo!(); - Ok(Some(self)) + fn head (&'s self) -> DslPerhaps { + Ok(peek(&self.src()[1..])?.map(Into::into)) } - fn exp_tail (&self) -> DslPerhaps { - todo!(); - Ok(Some(self)) - } - fn exp_next (&mut self) -> DslPerhaps { - todo!(); - Ok(Some(self)) + fn tail (&'s self) -> DslPerhaps { + Ok(if let Some((head_start, head_len)) = seek(&self.src()[1..])? { + peek(&self.src()[(1+head_start+head_len)..])?.map(Into::into) + } else { + None + }) } } -impl DslExp for D {} +//impl<'s, D: DslExp<'s>> DslExp<'s> for Option {} +impl<'s, D: Dsl + From<&'s str>> DslExp<'s> for D {} def_peek_seek!(exp_peek, exp_seek, exp_seek_start, exp_seek_length); const fn is_exp_start (c: char) -> bool { matches!(c, '(') } const fn is_exp_end (c: char) -> bool { matches!(c, ')') } diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 0bec9a8..8e87506 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -54,20 +54,37 @@ impl EventMap { } /// Create event map from string. pub fn from_source (source: impl AsRef) -> Usually where E: From> { - Self::from_dsl(source.as_ref()) + Self::from_dsl(&mut source.as_ref()) } /// Create event map from DSL tokenizer. - pub fn from_dsl (mut dsl: impl Dsl) -> Usually where E: From> { + pub fn from_dsl <'s, D: DslExp<'s> + 's> (dsl: &'s mut D) -> Usually where E: From> { let mut map: Self = Default::default(); - while let Some(dsl) = dsl.exp_next()? { - if let Some(path) = dsl.text()? { - map.0.extend(Self::from_path(PathBuf::from(path.as_ref() as &str))?.0) - } else if dsl.exp_head()?.key()? == Some("if") { - todo!() - //map.add(sym.into(), Binding::from_dsl(dsl.exp_tail())?); - } else if let Some(sym) = dsl.exp_head()?.sym()? { - todo!() - //map.add(sym.into(), Binding::from_dsl(dsl.exp_tail())?); + let mut head: Option = dsl.head()?; + let mut tail: Option = dsl.tail()?; + loop { + if let Some(ref token) = head { + if let Some(ref text) = token.text()? { + map.0.extend(Self::from_path(PathBuf::from(text))?.0); + continue + } + //if let Some(ref exp) = token.exp()? { + ////_ if let Some(sym) = token.head()?.sym()?.as_ref() => { + ////todo!() + ////}, + ////_ if Some(&"if") == token.head()?.key()?.as_ref() => { + ////todo!() + ////}, + //return Err(format!("unexpected: {:?}", token.exp()).into()) + //} + return Err(format!("unexpected: {token:?}").into()) + } else { + break + } + if let Some(next) = tail { + head = next.head()?; + tail = next.tail()?; + } else { + break } } Ok(map) diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs index 2be276c..ed13cdc 100644 --- a/input/src/input_handle.rs +++ b/input/src/input_handle.rs @@ -35,43 +35,48 @@ pub trait Input: Sized { } } -/// Handle input -pub trait Handle { +flex_trait_mut!(Handle { fn handle (&mut self, _input: &E) -> Perhaps { Ok(None) } -} -impl> Handle for &mut H { - fn handle (&mut self, context: &E) -> Perhaps { - (*self).handle(context) - } -} -impl> Handle for Option { - fn handle (&mut self, context: &E) -> Perhaps { - if let Some(handle) = self { - handle.handle(context) - } else { - Ok(None) - } - } -} -impl Handle for Mutex where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.get_mut().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.lock().unwrap().handle(context) - } -} -impl Handle for RwLock where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.write().unwrap().handle(context) - } -} -impl Handle for Arc> where H: Handle { - fn handle (&mut self, context: &E) -> Perhaps { - self.write().unwrap().handle(context) - } -} +}); + +//pub trait Handle { + //fn handle (&mut self, _input: &E) -> Perhaps { + //Ok(None) + //} +//} +//impl> Handle for &mut H { + //fn handle (&mut self, context: &E) -> Perhaps { + //(*self).handle(context) + //} +//} +//impl> Handle for Option { + //fn handle (&mut self, context: &E) -> Perhaps { + //if let Some(handle) = self { + //handle.handle(context) + //} else { + //Ok(None) + //} + //} +//} +//impl Handle for Mutex where H: Handle { + //fn handle (&mut self, context: &E) -> Perhaps { + //self.get_mut().unwrap().handle(context) + //} +//} +//impl Handle for Arc> where H: Handle { + //fn handle (&mut self, context: &E) -> Perhaps { + //self.lock().unwrap().handle(context) + //} +//} +//impl Handle for RwLock where H: Handle { + //fn handle (&mut self, context: &E) -> Perhaps { + //self.write().unwrap().handle(context) + //} +//} +//impl Handle for Arc> where H: Handle { + //fn handle (&mut self, context: &E) -> Perhaps { + //self.write().unwrap().handle(context) + //} +//} diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index bd44c96..13a5d5c 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -56,13 +56,13 @@ impl ViewDef { let builtins = builtins_with_boxes_output(quote! { #output }) .map(|(builtin, builtin_ty)|match builtin { Single(name) => quote! { - if dsl.exp_head()?.key()? == Some(#name) { + if dsl.head()?.key()? == Some(#name) { return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl, ||format!("failed to load builtin").into())?.boxed())) } }, Prefix(name) => quote! { - if let Some(key) = dsl.exp_head()?.key()? && key.starts_with(#name) { + if let Some(key) = dsl.head()?.key()? && key.starts_with(#name) { return Ok(Some(#builtin_ty::from_dsl_or_else(self, dsl, ||format!("failed to load builtin").into())?.boxed())) } From 9f7d0efda5a71dfbb55c55dbff32c348b55dd870 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 29 Jul 2025 17:03:48 +0300 Subject: [PATCH 126/178] core, input, output, dsl: factor away 'flexi' traits --- core/src/core_macros.rs | 46 +++- dsl/src/dsl.rs | 84 ++++--- dsl/src/{dsl_types.rs => dsl_parse.rs} | 280 +++++++++++------------ input/src/{input_command.rs => input.rs} | 20 ++ input/src/input_dsl.rs | 93 +++++--- input/src/input_handle.rs | 82 ------- input/src/input_macros.rs | 24 +- input/src/input_test.rs | 28 +++ input/src/lib.rs | 33 +-- output/src/ops.rs | 2 +- proc/src/proc_flex.rs | 2 + 11 files changed, 355 insertions(+), 339 deletions(-) rename dsl/src/{dsl_types.rs => dsl_parse.rs} (63%) rename input/src/{input_command.rs => input.rs} (50%) delete mode 100644 input/src/input_handle.rs create mode 100644 input/src/input_test.rs create mode 100644 proc/src/proc_flex.rs diff --git a/core/src/core_macros.rs b/core/src/core_macros.rs index b819764..d5ada8f 100644 --- a/core/src/core_macros.rs +++ b/core/src/core_macros.rs @@ -1,5 +1,3 @@ -use crate::*; - /// Define and reexport submodules. #[macro_export] macro_rules! modules( ($($($feat:literal?)? $name:ident),* $(,)?) => { $( @@ -12,7 +10,37 @@ use crate::*; )* }; ); -/// Define a trait for various wrapper types. */ +/// Define a trait an implement it for read-only wrapper types. */ +#[macro_export] macro_rules! flex_trait ( + ($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(+$dep2:ident)*)? { + $(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)* + }) => { + pub trait $Trait $(<$($A: $T),+>)? $(:$dep$(+$dep2)*)? { + $(fn $fn (&$self $(,$arg:$ty)*) -> $ret $body)* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ { + $(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { + $(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (**$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> { + $(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })* + } + //impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> { + //$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { + //if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) } + //})* + //} + //impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> { + //$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { (*$self).lock().unwrap().$fn($($arg),*) })* + //} + //impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> { + //$(fn $fn (&$self $(,$arg:$ty)*) -> $ret { $self.read().unwrap().$fn($($arg),*) })* + //} + }); + +/// Define a trait an implement it for various mutation-enabled wrapper types. */ #[macro_export] macro_rules! flex_trait_mut ( ($Trait:ident $(<$($A:ident:$T:ident),+>)? { $(fn $fn:ident (&mut $self:ident $(, $arg:ident:$ty:ty)*) -> $ret:ty $body:block)* @@ -20,24 +48,24 @@ use crate::*; pub trait $Trait $(<$($A: $T),+>)? { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret $body)* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { (*$self).$fn($($arg),*) })* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for Option<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { if let Some(this) = $self { this.$fn($($arg),*) } else { Ok(None) } })* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Mutex<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.get_mut().unwrap().$fn($($arg),*) })* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::Mutex<_T_>> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.lock().unwrap().$fn($($arg),*) })* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::RwLock<_T_> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* } - impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> { + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<::std::sync::RwLock<_T_>> { $(fn $fn (&mut $self $(,$arg:$ty)*) -> $ret { $self.write().unwrap().$fn($($arg),*) })* } }; diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 8f83589..979585d 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -6,48 +6,78 @@ extern crate const_panic; use const_panic::PanicFmt; use std::fmt::Debug; -pub(crate) use ::tengri_core::*; + pub(crate) use std::error::Error; +pub(crate) use std::sync::Arc; + pub(crate) use konst::string::{str_range, char_indices}; pub(crate) use thiserror::Error; +pub(crate) use ::tengri_core::*; + pub(crate) use self::DslError::*; -mod dsl_conv; pub use self::dsl_conv::*; -mod dsl_types; pub use self::dsl_types::*; -#[cfg(test)] mod dsl_test; +mod dsl_conv; +pub use self::dsl_conv::*; -pub trait Dsl: Debug + Send + Sync + Sized { - fn src (&self) -> &str; -} +mod dsl_parse; +pub(crate) use self::dsl_parse::*; +pub mod parse { pub use crate::dsl_parse::*; } -impl<'s> Dsl for &'s str { - fn src (&self) -> &str { self } -} +#[cfg(test)] +mod dsl_test; -impl Dsl for String { - fn src (&self) -> &str { self.as_str() } -} - -impl Dsl for std::sync::Arc { +flex_trait!(Dsl: Debug + Send + Sync + Sized { + fn src (&self) -> &str { + unreachable!("Dsl::src default impl") + } +}); +impl Dsl for Arc { fn src (&self) -> &str { self.as_ref() } } - -impl Dsl for &D { - fn src (&self) -> &str { (*self).src() } +impl<'s> Dsl for &'s str { + fn src (&self) -> &str { self.as_ref() } } - -impl Dsl for &mut D { - fn src (&self) -> &str { (**self).src() } -} - -impl Dsl for std::sync::Arc { - fn src (&self) -> &str { (*self).src() } -} - impl Dsl for Option { fn src (&self) -> &str { if let Some(dsl) = self { dsl.src() } else { "" } } } +impl DslExp for D {} +pub trait DslExp: Dsl { + fn exp (&self) -> DslPerhaps<&str> { + Ok(exp_peek(self.src())?) + } + fn head (&self) -> DslPerhaps<&str> { + Ok(peek(&self.src()[1..])?) + } + fn tail (&self) -> DslPerhaps<&str> { + Ok(if let Some((head_start, head_len)) = seek(&self.src()[1..])? { + peek(&self.src()[(1+head_start+head_len)..])? + } else { + None + }) + } +} + +impl DslSym for D {} +pub trait DslSym: Dsl { + fn sym (&self) -> DslPerhaps<&str> { crate::parse::sym_peek(self.src()) } +} + +impl DslKey for D {} +pub trait DslKey: Dsl { + fn key (&self) -> DslPerhaps<&str> { crate::parse::key_peek(self.src()) } +} + +impl DslText for D {} +pub trait DslText: Dsl { + fn text (&self) -> DslPerhaps<&str> { crate::parse::text_peek(self.src()) } +} + +impl DslNum for D {} +pub trait DslNum: Dsl { + fn num (&self) -> DslPerhaps<&str> { crate::parse::num_peek(self.src()) } +} + /// DSL-specific result type. pub type DslResult = Result; diff --git a/dsl/src/dsl_types.rs b/dsl/src/dsl_parse.rs similarity index 63% rename from dsl/src/dsl_types.rs rename to dsl/src/dsl_parse.rs index c9d69a1..25eef20 100644 --- a/dsl/src/dsl_types.rs +++ b/dsl/src/dsl_parse.rs @@ -1,16 +1,18 @@ use crate::*; + macro_rules! iter_chars(($source:expr => |$i:ident, $c:ident|$val:expr)=>{ while let Some((($i, $c), next)) = char_indices($source).next() { $source = next.as_str(); $val } }); + macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ /// Find a slice corrensponding to a syntax token. - const fn $peek (source: &str) -> DslPerhaps<&str> { + pub const fn $peek (source: &str) -> DslPerhaps<&str> { match $seek(source) { Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), Ok(None) => Ok(None), Err(e) => Err(e) } } /// Find a start and length corresponding to a syntax token. - const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { + pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { match $seek_start(source) { Ok(Some(start)) => match $seek_length(str_range(source, start, source.len() - start)) { Ok(Some(length)) => Ok(Some((start, length))), @@ -20,6 +22,130 @@ macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_l Ok(None) => Ok(None), Err(e) => Err(e) } } }); +def_peek_seek!(exp_peek, exp_seek, exp_seek_start, exp_seek_length); +pub const fn is_exp_start (c: char) -> bool { matches!(c, '(') } +pub const fn is_exp_end (c: char) -> bool { matches!(c, ')') } +pub const fn exp_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_exp_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn exp_seek_length (mut source: &str) -> DslPerhaps { + let mut depth = 0; + iter_chars!(source => |i, c| if is_exp_start(c) { + depth += 1; + } else if is_exp_end(c) { + if depth == 0 { + return Err(Unexpected(c)) + } else if depth == 1 { + return Ok(Some(i)) + } else { + depth -= 1; + } + }); + Ok(None) +} + +def_peek_seek!(sym_peek, sym_seek, sym_seek_start, sym_seek_length); +pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } +pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } +pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn sym_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_sym_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn sym_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_sym_end(c) { + return Ok(Some(i)) + } else if !is_sym_char(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} + +def_peek_seek!(key_peek, key_seek, key_seek_start, key_seek_length); +pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } +pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } +pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn key_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_key_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn key_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_key_end(c) { + return Ok(Some(i)) + } else if !is_key_char(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} + +def_peek_seek!(text_peek, text_seek, text_seek_start, text_seek_length); +pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } +pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } +pub const fn text_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_text_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn text_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_text_end(c) { return Ok(Some(i)) }); + Ok(None) +} + +def_peek_seek!(num_peek, num_seek, num_seek_start, num_seek_length); +pub const fn num_seek_start (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_digit(c) { + return Ok(Some(i)); + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn num_seek_length (mut source: &str) -> DslPerhaps { + iter_chars!(source => |i, c| if is_num_end(c) { + return Ok(Some(i)) + } else if !is_digit(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } +pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn to_number (digits: &str) -> Result { + let mut iter = char_indices(digits); + let mut value = 0; + while let Some(((_, c), next)) = iter.next() { + match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Err(e) => return Err(e), + } + iter = next; + } + Ok(value) +} +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Err(Unexpected(c)) + }) +} + pub const fn peek (mut src: &str) -> DslPerhaps<&str> { Ok(Some(match () { _ if let Ok(Some(exp)) = exp_peek(src) => exp, @@ -52,152 +178,6 @@ pub const fn seek (mut src: &str) -> DslPerhaps<(usize, usize)> { })) } -const fn is_whitespace (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } -pub trait DslExp<'s>: Dsl + From<&'s str> { - fn exp (&'s self) -> DslPerhaps { - Ok(exp_peek(self.src())?.map(Into::into)) - } - fn head (&'s self) -> DslPerhaps { - Ok(peek(&self.src()[1..])?.map(Into::into)) - } - fn tail (&'s self) -> DslPerhaps { - Ok(if let Some((head_start, head_len)) = seek(&self.src()[1..])? { - peek(&self.src()[(1+head_start+head_len)..])?.map(Into::into) - } else { - None - }) - } -} -//impl<'s, D: DslExp<'s>> DslExp<'s> for Option {} -impl<'s, D: Dsl + From<&'s str>> DslExp<'s> for D {} -def_peek_seek!(exp_peek, exp_seek, exp_seek_start, exp_seek_length); -const fn is_exp_start (c: char) -> bool { matches!(c, '(') } -const fn is_exp_end (c: char) -> bool { matches!(c, ')') } -const fn exp_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_exp_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn exp_seek_length (mut source: &str) -> DslPerhaps { - let mut depth = 0; - iter_chars!(source => |i, c| if is_exp_start(c) { - depth += 1; - } else if is_exp_end(c) { - if depth == 0 { - return Err(Unexpected(c)) - } else if depth == 1 { - return Ok(Some(i)) - } else { - depth -= 1; - } - }); - Ok(None) -} - -pub trait DslSym: Dsl { fn sym (&self) -> DslPerhaps<&str> { sym_peek(self.src()) } } -impl DslSym for D {} -def_peek_seek!(sym_peek, sym_seek, sym_seek_start, sym_seek_length); -const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } -const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -const fn sym_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_sym_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn sym_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_sym_end(c) { - return Ok(Some(i)) - } else if !is_sym_char(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} - -pub trait DslKey: Dsl { fn key (&self) -> DslPerhaps<&str> { key_peek(self.src()) } } -impl DslKey for D {} -def_peek_seek!(key_peek, key_seek, key_seek_start, key_seek_length); -const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } -const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } -const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -const fn key_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_key_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn key_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_key_end(c) { - return Ok(Some(i)) - } else if !is_key_char(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} - -pub trait DslText: Dsl { fn text (&self) -> DslPerhaps<&str> { text_peek(self.src()) } } -impl DslText for D {} -def_peek_seek!(text_peek, text_seek, text_seek_start, text_seek_length); -const fn is_text_start (c: char) -> bool { matches!(c, '"') } -const fn is_text_end (c: char) -> bool { matches!(c, '"') } -const fn text_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_text_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn text_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_text_end(c) { return Ok(Some(i)) }); - Ok(None) -} - -pub trait DslNum: Dsl { fn num (&self) -> DslPerhaps<&str> { num_peek(self.src()) } } -impl DslNum for D {} -def_peek_seek!(num_peek, num_seek, num_seek_start, num_seek_length); -const fn num_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_digit(c) { - return Ok(Some(i)); - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn num_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_num_end(c) { - return Ok(Some(i)) - } else if !is_digit(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } -const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn to_number (digits: &str) -> Result { - let mut iter = char_indices(digits); - let mut value = 0; - while let Some(((_, c), next)) = iter.next() { - match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Err(e) => return Err(e), - } - iter = next; - } - Ok(value) -} -pub const fn to_digit (c: char) -> Result { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Err(Unexpected(c)) - }) +pub const fn is_whitespace (c: char) -> bool { + matches!(c, ' '|'\n'|'\r'|'\t') } diff --git a/input/src/input_command.rs b/input/src/input.rs similarity index 50% rename from input/src/input_command.rs rename to input/src/input.rs index 6051277..7b451ee 100644 --- a/input/src/input_command.rs +++ b/input/src/input.rs @@ -1,5 +1,25 @@ use crate::*; +/// Event source +pub trait Input: Sized { + /// Type of input event + type Event; + /// Result of handling input + type Handled; // TODO: make this an Option> 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); +} + +flex_trait_mut!(Handle { + fn handle (&mut self, _input: &E) -> Perhaps { + Ok(None) + } +}); + pub trait Command: Send + Sync + Sized { fn execute (self, state: &mut S) -> Perhaps; fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 8e87506..855272d 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,8 +1,10 @@ use crate::*; + /// Map of each event (e.g. key combination) to /// all command expressions bound to it by /// all loaded input layers. type EventMapImpl = BTreeMap>>; + /// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation @@ -14,8 +16,26 @@ type EventMapImpl = BTreeMap>>; /// that .event()binding's value is returned. #[derive(Debug)] pub struct EventMap(EventMapImpl); + +/// An input binding. +#[derive(Debug)] +pub struct Binding { + pub command: C, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} + +/// Input bindings are only returned if this evaluates to true +pub struct Condition(Boxbool + Send + Sync>); + +impl_debug!(Condition |self, w| { write!(w, "*") }); + /// Default is always empty map regardless if `E` and `C` implement [Default]. -impl Default for EventMap { fn default () -> Self { Self(Default::default()) } } +impl Default for EventMap { + fn default () -> Self { Self(Default::default()) } +} + impl EventMap { /// Create a new event map pub fn new () -> Self { @@ -28,7 +48,7 @@ impl EventMap { } /// Add a binding to an event map. pub fn add (&mut self, event: E, binding: Binding) -> &mut Self { - if (!self.0.contains_key(&event)) { + if !self.0.contains_key(&event) { self.0.insert(event.clone(), Default::default()); } self.0.get_mut(&event).unwrap().push(binding); @@ -57,47 +77,46 @@ impl EventMap { Self::from_dsl(&mut source.as_ref()) } /// Create event map from DSL tokenizer. - pub fn from_dsl <'s, D: DslExp<'s> + 's> (dsl: &'s mut D) -> Usually where E: From> { + pub fn from_dsl <'s, > (dsl: &'s mut impl Dsl) -> Usually where E: From> { let mut map: Self = Default::default(); - let mut head: Option = dsl.head()?; - let mut tail: Option = dsl.tail()?; - loop { - if let Some(ref token) = head { - if let Some(ref text) = token.text()? { - map.0.extend(Self::from_path(PathBuf::from(text))?.0); - continue + if let Some(dsl) = dsl.exp()? { + let mut head = dsl.head()?; + let mut tail = dsl.tail()?; + loop { + if let Some(ref token) = head { + if let Some(ref text) = token.text()? { + map.0.extend(Self::from_path(PathBuf::from(text))?.0); + continue + } + //if let Some(ref exp) = token.exp()? { + ////_ if let Some(sym) = token.head()?.sym()?.as_ref() => { + ////todo!() + ////}, + ////_ if Some(&"if") == token.head()?.key()?.as_ref() => { + ////todo!() + ////}, + //return Err(format!("unexpected: {:?}", token.exp()).into()) + //} + return Err(format!("unexpected: {token:?}").into()) + } else { + break + } + if let Some(next) = tail { + head = next.head()?; + tail = next.tail()?; + } else { + break } - //if let Some(ref exp) = token.exp()? { - ////_ if let Some(sym) = token.head()?.sym()?.as_ref() => { - ////todo!() - ////}, - ////_ if Some(&"if") == token.head()?.key()?.as_ref() => { - ////todo!() - ////}, - //return Err(format!("unexpected: {:?}", token.exp()).into()) - //} - return Err(format!("unexpected: {token:?}").into()) - } else { - break - } - if let Some(next) = tail { - head = next.head()?; - tail = next.tail()?; - } else { - break } + } else if let Some(text) = dsl.text()? { + todo!("load from file path") + } else { + todo!("return error for invalid input") } Ok(map) } } -/// An input binding. -#[derive(Debug)] -pub struct Binding { - pub command: C, - pub condition: Option, - pub description: Option>, - pub source: Option>, -} + impl Binding { fn from_dsl (dsl: impl Dsl) -> Usually { let mut command: Option = None; @@ -111,8 +130,6 @@ impl Binding { } } } -pub struct Condition(Boxbool + Send + Sync>); -impl_debug!(Condition |self, w| { write!(w, "*") }); fn unquote (x: &str) -> &str { let mut chars = x.chars(); diff --git a/input/src/input_handle.rs b/input/src/input_handle.rs deleted file mode 100644 index ed13cdc..0000000 --- a/input/src/input_handle.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::*; -use std::sync::{Mutex, Arc, RwLock}; - -/// Event source -pub trait Input: Sized { - /// Type of input event - type Event; - /// Result of handling input - type Handled; // TODO: make this an Option> 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 ::tengri::input::Handle for $Struct { - fn handle (&mut $self, $input: &E) -> Perhaps { - $handler - } - } - }; - ($E:ty: |$self:ident:$Struct:ty,$input:ident|$handler:expr) => { - impl ::tengri::input::Handle<$E> for $Struct { - fn handle (&mut $self, $input: &$E) -> - Perhaps<<$E as ::tengri::input::Input>::Handled> - { - $handler - } - } - } -} - -flex_trait_mut!(Handle { - fn handle (&mut self, _input: &E) -> Perhaps { - Ok(None) - } -}); - -//pub trait Handle { - //fn handle (&mut self, _input: &E) -> Perhaps { - //Ok(None) - //} -//} -//impl> Handle for &mut H { - //fn handle (&mut self, context: &E) -> Perhaps { - //(*self).handle(context) - //} -//} -//impl> Handle for Option { - //fn handle (&mut self, context: &E) -> Perhaps { - //if let Some(handle) = self { - //handle.handle(context) - //} else { - //Ok(None) - //} - //} -//} -//impl Handle for Mutex where H: Handle { - //fn handle (&mut self, context: &E) -> Perhaps { - //self.get_mut().unwrap().handle(context) - //} -//} -//impl Handle for Arc> where H: Handle { - //fn handle (&mut self, context: &E) -> Perhaps { - //self.lock().unwrap().handle(context) - //} -//} -//impl Handle for RwLock where H: Handle { - //fn handle (&mut self, context: &E) -> Perhaps { - //self.write().unwrap().handle(context) - //} -//} -//impl Handle for Arc> where H: Handle { - //fn handle (&mut self, context: &E) -> Perhaps { - //self.write().unwrap().handle(context) - //} -//} diff --git a/input/src/input_macros.rs b/input/src/input_macros.rs index ae5758c..e47b902 100644 --- a/input/src/input_macros.rs +++ b/input/src/input_macros.rs @@ -1,10 +1,30 @@ -/** Implement `Command` for given `State` and `handler` */ +/// Implement [Command] for given `State` and `handler` #[macro_export] macro_rules! command { ($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => { - impl$(<$($l),+>)? Command<$State> for $Command { + impl$(<$($l),+>)? ::tengri::input::Command<$State> for $Command { fn execute ($self, $state: &mut $State) -> Perhaps { Ok($handler) } } }; } + +/// Implement [Handle] for given `State` and `handler`. +#[macro_export] macro_rules! handle { + (|$self:ident:$State:ty,$input:ident|$handler:expr) => { + impl ::tengri::input::Handle for $State { + fn handle (&mut $self, $input: &E) -> Perhaps { + $handler + } + } + }; + ($E:ty: |$self:ident:$State:ty,$input:ident|$handler:expr) => { + impl ::tengri::input::Handle<$E> for $State { + fn handle (&mut $self, $input: &$E) -> + Perhaps<<$E as ::tengri::input::Input>::Handled> + { + $handler + } + } + } +} diff --git a/input/src/input_test.rs b/input/src/input_test.rs new file mode 100644 index 0000000..8373435 --- /dev/null +++ b/input/src/input_test.rs @@ -0,0 +1,28 @@ +use crate::*; + +#[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(()) +} + +#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { + let _keymap = CstIter::new(""); + Ok(()) +} + diff --git a/input/src/lib.rs b/input/src/lib.rs index 0779092..f219c64 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -3,41 +3,14 @@ pub(crate) use std::fmt::Debug; pub(crate) use std::sync::Arc; -pub(crate) use std::collections::{BTreeMap, HashMap}; +pub(crate) use std::collections::BTreeMap; pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; mod input_macros; -mod input_command; pub use self::input_command::*; -mod input_handle; pub use self::input_handle::*; - +mod input; pub use self::input::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; #[cfg(feature = "dsl")] mod input_dsl; #[cfg(feature = "dsl")] pub use self::input_dsl::*; - -#[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(()) -} - -#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { - let _keymap = CstIter::new(""); - Ok(()) -} +#[cfg(test)] mod input_test; diff --git a/output/src/ops.rs b/output/src/ops.rs index 846c86c..bdbcff0 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -373,7 +373,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{ /// the layout elements that are provided by this crate. #[cfg(feature = "dsl")] mod ops_dsl { use crate::*; - use ::tengri_dsl::*; + //use ::tengri_dsl::*; //macro_rules! dsl { //($( diff --git a/proc/src/proc_flex.rs b/proc/src/proc_flex.rs new file mode 100644 index 0000000..039dd7d --- /dev/null +++ b/proc/src/proc_flex.rs @@ -0,0 +1,2 @@ +// TODO: #[derive(Flex, FlexMut, FlexOption, FlexResult ... ?)] +// better derive + annotations From 85c305385bc08ef805afb113f677c510027a7234 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 29 Jul 2025 22:07:03 +0300 Subject: [PATCH 127/178] fix(input): wat --- input/src/input_dsl.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 855272d..bb3c353 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -18,7 +18,7 @@ type EventMapImpl = BTreeMap>>; pub struct EventMap(EventMapImpl); /// An input binding. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Binding { pub command: C, pub condition: Option, @@ -27,7 +27,8 @@ pub struct Binding { } /// Input bindings are only returned if this evaluates to true -pub struct Condition(Boxbool + Send + Sync>); +#[derive(Clone)] +pub struct Condition(Arcbool + Send + Sync>>); impl_debug!(Condition |self, w| { write!(w, "*") }); From 9e0b7be9a9c80b5df52854ab426bc60b794931ed Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 31 Jul 2025 21:00:13 +0300 Subject: [PATCH 128/178] fix(dsl): tail condition --- dsl/src/dsl.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 979585d..d8de0f5 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -6,19 +6,13 @@ extern crate const_panic; use const_panic::PanicFmt; use std::fmt::Debug; - pub(crate) use std::error::Error; pub(crate) use std::sync::Arc; - pub(crate) use konst::string::{str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use ::tengri_core::*; - pub(crate) use self::DslError::*; - -mod dsl_conv; -pub use self::dsl_conv::*; - +mod dsl_conv; pub use self::dsl_conv::*; mod dsl_parse; pub(crate) use self::dsl_parse::*; pub mod parse { pub use crate::dsl_parse::*; } @@ -47,11 +41,20 @@ pub trait DslExp: Dsl { Ok(exp_peek(self.src())?) } fn head (&self) -> DslPerhaps<&str> { - Ok(peek(&self.src()[1..])?) + let start = 1; + let src = self.src(); + let src = &src[start.min(src.len().saturating_sub(1))..]; + peek(src) } fn tail (&self) -> DslPerhaps<&str> { - Ok(if let Some((head_start, head_len)) = seek(&self.src()[1..])? { - peek(&self.src()[(1+head_start+head_len)..])? + let start = 1; + let src = self.src(); + let src = &src[start.min(src.len().saturating_sub(1))..]; + Ok(if let Some((head_start, head_len)) = seek(src)? { + let start = 1 + head_start + head_len; + let src = self.src(); + let src = &src[start.min(src.len().saturating_sub(1))..]; + peek(src)? } else { None }) From 643658ab16d0da3107eb4d212ea47090917eecb8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 31 Jul 2025 22:33:59 +0300 Subject: [PATCH 129/178] dsl: fixed expression handling --- dsl/README.md | 21 +++ dsl/src/dsl.rs | 328 ++++++++++++++++++++++++++++++++++--------- dsl/src/dsl_parse.rs | 183 ------------------------ dsl/src/dsl_test.rs | 97 +++++++++++++ input/src/lib.rs | 7 - tui/src/lib.rs | 18 +-- 6 files changed, 384 insertions(+), 270 deletions(-) delete mode 100644 dsl/src/dsl_parse.rs diff --git a/dsl/README.md b/dsl/README.md index 97f1d2c..a5bcf22 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -92,3 +92,24 @@ or configuration statements, and look like this: * [ ] const parse * [ ] live reload * [ ] serialize modified code back to original indentation + +## implementation notes + +### `DslExp` trait behavior + +this is the trait which differentiates "a thing" from +"a thing that is many things". + +|source |key|exp |head |tail | +|---------------|---|-------|---------|---------------| +|`a` |`a`|e0 |`a` |None | +|`(a)` |e1 |`a` |`(a)` |None | +|`a b c` |e2 |e0 |`a` |`b c` | +|`(a b c)` |e1 |`a b c`|`(a b c)`| | +|`(a b c) d e` |e1 |e3 |`(a b c)`|`d e` | +|`a (b c d) e f`|e1 |e0 |`a` |`(b c d) e f` | + +* e0: Unexpected 'a' +* e1: Unexpected '(' +* e2: Unexpected 'b' +* e3: Unexpected 'd' diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index d8de0f5..c43e2f1 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -6,87 +6,45 @@ extern crate const_panic; use const_panic::PanicFmt; use std::fmt::Debug; -pub(crate) use std::error::Error; -pub(crate) use std::sync::Arc; -pub(crate) use konst::string::{str_range, char_indices}; +pub(crate) use std::{error::Error, sync::Arc}; +pub(crate) use konst::{iter::for_each, string::{str_from, str_range, char_indices}}; pub(crate) use thiserror::Error; pub(crate) use ::tengri_core::*; pub(crate) use self::DslError::*; mod dsl_conv; pub use self::dsl_conv::*; -mod dsl_parse; -pub(crate) use self::dsl_parse::*; -pub mod parse { pub use crate::dsl_parse::*; } - -#[cfg(test)] -mod dsl_test; - +#[cfg(test)] mod dsl_test; flex_trait!(Dsl: Debug + Send + Sync + Sized { - fn src (&self) -> &str { - unreachable!("Dsl::src default impl") - } + fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } }); -impl Dsl for Arc { - fn src (&self) -> &str { self.as_ref() } -} -impl<'s> Dsl for &'s str { - fn src (&self) -> &str { self.as_ref() } -} +impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } impl Dsl for Option { - fn src (&self) -> &str { if let Some(dsl) = self { dsl.src() } else { "" } } + fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} } - -impl DslExp for D {} -pub trait DslExp: Dsl { - fn exp (&self) -> DslPerhaps<&str> { - Ok(exp_peek(self.src())?) - } - fn head (&self) -> DslPerhaps<&str> { - let start = 1; - let src = self.src(); - let src = &src[start.min(src.len().saturating_sub(1))..]; - peek(src) - } - fn tail (&self) -> DslPerhaps<&str> { - let start = 1; - let src = self.src(); - let src = &src[start.min(src.len().saturating_sub(1))..]; - Ok(if let Some((head_start, head_len)) = seek(src)? { - let start = 1 + head_start + head_len; - let src = self.src(); - let src = &src[start.min(src.len().saturating_sub(1))..]; - peek(src)? - } else { - None - }) - } +impl Dsl for Result { + fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}} } - -impl DslSym for D {} -pub trait DslSym: Dsl { - fn sym (&self) -> DslPerhaps<&str> { crate::parse::sym_peek(self.src()) } +impl DslExp for D {} pub trait DslExp: Dsl { + fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} + fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} + fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail(self.head())))} } - -impl DslKey for D {} -pub trait DslKey: Dsl { - fn key (&self) -> DslPerhaps<&str> { crate::parse::key_peek(self.src()) } +impl DslText for D {} pub trait DslText: Dsl { + fn text (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(text_peek_only))} } - -impl DslText for D {} -pub trait DslText: Dsl { - fn text (&self) -> DslPerhaps<&str> { crate::parse::text_peek(self.src()) } +impl DslSym for D {} pub trait DslSym: Dsl { + fn sym (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(sym_peek_only))} } - -impl DslNum for D {} -pub trait DslNum: Dsl { - fn num (&self) -> DslPerhaps<&str> { crate::parse::num_peek(self.src()) } +impl DslKey for D {} pub trait DslKey: Dsl { + fn key (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(key_peek_only))} +} +impl DslNum for D {} pub trait DslNum: Dsl { + fn num (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(num_peek_only))} } - /// DSL-specific result type. pub type DslResult = Result; - /// DSL-specific optional result type. pub type DslPerhaps = Result, DslError>; - /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { @@ -97,3 +55,245 @@ pub enum DslError { #[error("parse failed: error #{0}")] Code(u8), #[error("end reached")] End } +#[macro_export] macro_rules! dsl_for_each (($dsl:expr => |$next:ident|$body:expr)=>{ + let mut dsl: Arc = $dsl.src().into(); + let mut $next: Option> = dsl.next()?.map(Into::into); + let mut rest: Option> = dsl.rest()?.map(Into::into); + loop { + if let Some($next) = $next { $body } else { break }; + if let Some(next) = rest { + $next = next.next()?.map(Into::into); + rest = next.rest()?.map(Into::into); + } else { + break + } + } +}); +fn ok_flat (x: Option>) -> DslPerhaps { Ok(x.transpose()?.flatten()) } +fn peek_tail <'a> (head: DslPerhaps<&'a str>) -> impl Fn(&'a str)->DslPerhaps<&'a str> { + move|src|match head { + Ok(Some(next)) => { + let src = &src[src.len().min(1 + next.len())..]; + for c in src.chars() { if !is_whitespace(c) { return Ok(Some(src)) } } + Ok(None) + }, + e => e + } +} +macro_rules! def_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ + /// Find a slice corrensponding to a syntax token. + pub const fn $peek (source: &str) -> DslPerhaps<&str> { + match $seek(source) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), + } + } + /// Find a slice corrensponding to a syntax token + /// but return an error if it isn't the only thing + /// in the source. + pub const fn $peek_only (source: &str) -> DslPerhaps<&str> { + match $seek(source) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => { + if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } + Ok(Some(str_range(source, start, start + length))) + } + } + } + /// Find a start and length corresponding to a syntax token. + pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { + match $seek_start(source) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some(start)) => match $seek_length(str_from(source, start)) { + Ok(Some(length)) => Ok(Some((start, length))), + Ok(None) => Ok(None), + Err(e) => Err(e), + }, + } + } +}); +def_peek_seek!(exp_peek, exp_peek_only, exp_seek, exp_seek_start, exp_seek_length); +pub const fn exp_peek_inner (source: &str) -> DslPerhaps<&str> { + match exp_peek(source) { + Ok(Some(peeked)) => { + let len = peeked.len(); + let start = if len > 0 { 1 } else { 0 }; + Ok(Some(str_range(source, start, start + len.saturating_sub(2)))) + }, + e => e + } +} +pub const fn exp_peek_inner_only (source: &str) -> DslPerhaps<&str> { + match exp_seek(source) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => { + if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } + let peeked = str_range(source, start, start + length); + let len = peeked.len(); + let start = if len > 0 { 1 } else { 0 }; + Ok(Some(str_range(peeked, start, start + len.saturating_sub(2)))) + }, + } +} +pub const fn is_exp_start (c: char) -> bool { c == '(' } +pub const fn is_exp_end (c: char) -> bool { c == ')' } +pub const fn exp_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_exp_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn exp_seek_length (source: &str) -> DslPerhaps { + let mut depth = 0; + for_each!((i, c) in char_indices(source) => if is_exp_start(c) { + depth += 1; + } else if is_exp_end(c) { + if depth == 0 { + return Err(Unexpected(c)) + } else if depth == 1 { + return Ok(Some(i + 1)) + } else { + depth -= 1; + } + }); + Err(Incomplete) +} +def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); +pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } +pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } +pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn sym_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_sym_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn sym_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_sym_end(c) { + return Ok(Some(i)) + } else if !is_sym_char(c) { + return Err(Unexpected(c)) + }); + Ok(Some(source.len())) +} +def_peek_seek!(key_peek, key_peek_only, key_seek, key_seek_start, key_seek_length); +pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|('a'..='z')) } +pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } +pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn key_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_key_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn key_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_key_end(c) { + return Ok(Some(i)) + } else if !is_key_char(c) { + return Err(Unexpected(c)) + }); + Ok(Some(source.len())) +} +def_peek_seek!(text_peek, text_peek_only, text_seek, text_seek_start, text_seek_length); +pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } +pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } +pub const fn text_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_text_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn text_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_text_end(c) { return Ok(Some(i)) }); + Ok(None) +} +def_peek_seek!(num_peek, num_peek_only, num_seek, num_seek_start, num_seek_length); +pub const fn num_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_digit(c) { + return Ok(Some(i)); + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn num_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_num_end(c) { + return Ok(Some(i)) + } else if !is_digit(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } +pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn to_number (digits: &str) -> Result { + let mut iter = char_indices(digits); + let mut value = 0; + while let Some(((_, c), next)) = iter.next() { + match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Err(e) => return Err(e), + } + iter = next; + } + Ok(value) +} +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Err(Unexpected(c)) + }) +} +pub const fn peek (src: &str) -> DslPerhaps<&str> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_peek(src) => exp, + _ if let Ok(Some(sym)) = sym_peek(src) => sym, + _ if let Ok(Some(key)) = key_peek(src) => key, + _ if let Ok(Some(num)) = num_peek(src) => num, + _ if let Ok(Some(text)) = text_peek(src) => text, + _ => { + for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} +pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_seek(src) => exp, + _ if let Ok(Some(sym)) = sym_seek(src) => sym, + _ if let Ok(Some(key)) = key_seek(src) => key, + _ if let Ok(Some(num)) = num_seek(src) => num, + _ if let Ok(Some(text)) = text_seek(src) => text, + _ => { + for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} +pub const fn is_whitespace (c: char) -> bool { + matches!(c, ' '|'\n'|'\r'|'\t') +} +pub const fn no_trailing_non_whitespace (source: &str, offset: usize) -> DslResult<()> { + let tail = str_range(source, offset, source.len()); + for_each!((_, c) in char_indices(tail) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(()) +} diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs deleted file mode 100644 index 25eef20..0000000 --- a/dsl/src/dsl_parse.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::*; - -macro_rules! iter_chars(($source:expr => |$i:ident, $c:ident|$val:expr)=>{ - while let Some((($i, $c), next)) = char_indices($source).next() { - $source = next.as_str(); $val } }); - -macro_rules! def_peek_seek(($peek:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ - /// Find a slice corrensponding to a syntax token. - pub const fn $peek (source: &str) -> DslPerhaps<&str> { - match $seek(source) { - Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), - Ok(None) => Ok(None), - Err(e) => Err(e) } } - /// Find a start and length corresponding to a syntax token. - pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { - match $seek_start(source) { - Ok(Some(start)) => match $seek_length(str_range(source, start, source.len() - start)) { - Ok(Some(length)) => Ok(Some((start, length))), - Ok(None) => Ok(None), - Err(e) => Err(e), - }, - Ok(None) => Ok(None), - Err(e) => Err(e) } } }); - -def_peek_seek!(exp_peek, exp_seek, exp_seek_start, exp_seek_length); -pub const fn is_exp_start (c: char) -> bool { matches!(c, '(') } -pub const fn is_exp_end (c: char) -> bool { matches!(c, ')') } -pub const fn exp_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_exp_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn exp_seek_length (mut source: &str) -> DslPerhaps { - let mut depth = 0; - iter_chars!(source => |i, c| if is_exp_start(c) { - depth += 1; - } else if is_exp_end(c) { - if depth == 0 { - return Err(Unexpected(c)) - } else if depth == 1 { - return Ok(Some(i)) - } else { - depth -= 1; - } - }); - Ok(None) -} - -def_peek_seek!(sym_peek, sym_seek, sym_seek_start, sym_seek_length); -pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } -pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn sym_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_sym_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn sym_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_sym_end(c) { - return Ok(Some(i)) - } else if !is_sym_char(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} - -def_peek_seek!(key_peek, key_seek, key_seek_start, key_seek_length); -pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|'a'..='z') } -pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } -pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn key_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_key_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn key_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_key_end(c) { - return Ok(Some(i)) - } else if !is_key_char(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} - -def_peek_seek!(text_peek, text_seek, text_seek_start, text_seek_length); -pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } -pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } -pub const fn text_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_text_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn text_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_text_end(c) { return Ok(Some(i)) }); - Ok(None) -} - -def_peek_seek!(num_peek, num_seek, num_seek_start, num_seek_length); -pub const fn num_seek_start (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_digit(c) { - return Ok(Some(i)); - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn num_seek_length (mut source: &str) -> DslPerhaps { - iter_chars!(source => |i, c| if is_num_end(c) { - return Ok(Some(i)) - } else if !is_digit(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } -pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn to_number (digits: &str) -> Result { - let mut iter = char_indices(digits); - let mut value = 0; - while let Some(((_, c), next)) = iter.next() { - match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Err(e) => return Err(e), - } - iter = next; - } - Ok(value) -} -pub const fn to_digit (c: char) -> Result { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Err(Unexpected(c)) - }) -} - -pub const fn peek (mut src: &str) -> DslPerhaps<&str> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_peek(src) => exp, - _ if let Ok(Some(sym)) = sym_peek(src) => sym, - _ if let Ok(Some(key)) = key_peek(src) => key, - _ if let Ok(Some(num)) = num_peek(src) => num, - _ if let Ok(Some(text)) = text_peek(src) => text, - _ => { - iter_chars!(src => |_i, c| if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} - -pub const fn seek (mut src: &str) -> DslPerhaps<(usize, usize)> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_seek(src) => exp, - _ if let Ok(Some(sym)) = sym_seek(src) => sym, - _ if let Ok(Some(key)) = key_seek(src) => key, - _ if let Ok(Some(num)) = num_seek(src) => num, - _ if let Ok(Some(text)) = text_seek(src) => text, - _ => { - iter_chars!(src => |_i, c| if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} - -pub const fn is_whitespace (c: char) -> bool { - matches!(c, ' '|'\n'|'\r'|'\t') -} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs index e69de29..05f0308 100644 --- a/dsl/src/dsl_test.rs +++ b/dsl/src/dsl_test.rs @@ -0,0 +1,97 @@ +use crate::*; +macro_rules!is_some(($exp:expr, $val:expr)=>{assert_eq!($exp, Ok(Some($val)))};); +macro_rules!is_none(($exp:expr)=>{assert_eq!($exp, Ok(None))};); +macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())}; + ($exp:expr, $err:expr)=>{assert_eq!($exp, Err($err))};); +#[test] fn test_exp () -> Result<(), DslError> { + let e0 = DslError::Unexpected('a'); + let e1 = DslError::Unexpected('('); + let e2 = DslError::Unexpected('b'); + let e3 = DslError::Unexpected('d'); + let check = |src: &str, key, exp, head, tail|{ + assert_eq!(src.key(), key, "{src}"); + assert_eq!(src.exp(), exp, "{src}"); + assert_eq!(src.head(), head, "{src}"); + assert_eq!(src.tail(), tail, "{src}"); + }; + check("a", Ok(Some("a")), Err(e0), Ok(Some("a")), Ok(None)); + check("(a)", Err(e1), Ok(Some("a")), Ok(Some("(a)")), Ok(None)); + check("a b c", Err(e2), Err(e0), Ok(Some("a")), Ok(Some("b c"))); + check("(a b c)", Err(e1), Ok(Some("a b c")), Ok(Some("(a b c)")), Ok(None)); + check("(a b c) d e f", Err(e1), Err(e3), Ok(Some("(a b c)")), Ok(Some("d e f"))); + check("a (b c d) e f", Err(e1), Err(e0), Ok(Some("a")), Ok(Some("(b c d) e f"))); + + assert!(is_whitespace(' ')); + assert!(!is_key_start(' ')); + assert!(is_key_start('f')); + + is_some!(key_seek_start("foo"), 0); + is_some!(key_seek_start("foo "), 0); + is_some!(key_seek_start(" foo "), 1); + is_some!(key_seek_length(&" foo "[1..]), 3); + is_some!(key_seek("foo"), (0, 3)); + is_some!(key_peek("foo"), "foo"); + is_some!(key_seek("foo "), (0, 3)); + is_some!(key_peek("foo "), "foo"); + is_some!(key_seek(" foo "), (1, 3)); + is_some!(key_peek(" foo "), "foo"); + + is_err!("(foo)".key()); + is_err!("foo".exp()); + + is_some!("(foo)".exp(), "foo"); + is_some!("(foo)".head(), "(foo)"); + is_none!("(foo)".tail()); + + is_some!("(foo bar baz)".exp(), "foo bar baz"); + is_some!("(foo bar baz)".head(), "(foo bar baz)"); + is_none!("(foo bar baz)".tail()); + + is_some!("(foo bar baz)".exp().head(), "foo"); + is_some!("(foo bar baz)".exp().tail(), "bar baz"); + is_some!("(foo bar baz)".exp().tail().head(), "bar"); + is_some!("(foo bar baz)".exp().tail().tail(), "baz"); + + is_err!("foo".exp()); + is_some!("foo".key(), "foo"); + is_some!(" foo".key(), "foo"); + is_some!(" foo ".key(), "foo"); + + is_some!(" foo ".head(), "foo"); + //assert_eq!(" foo ".head().head(), Ok(None)); + is_none!(" foo ".head().tail()); + is_none!(" foo ".tail()); + is_none!(" foo ".tail().head()); + is_none!(" foo ".tail().tail()); + + assert_eq!(" foo bar ".head(), Ok(Some("foo"))); + //assert_eq!(" foo bar ".head().head(), Ok(None)); + assert_eq!(" foo bar ".head().tail(), Ok(None)); + assert_eq!(" foo bar ".tail(), Ok(Some(" bar "))); + assert_eq!(" foo bar ".tail().head(), Ok(Some("bar"))); + assert_eq!(" foo bar ".tail().tail(), Ok(None)); + + assert_eq!(" (foo) ".head(), Ok(Some("(foo)"))); + //assert_eq!(" (foo) ".head().head(), Ok(Some("foo"))); + //assert_eq!(" (foo) ".head().head().head(), Ok(None)); + assert_eq!(" (foo) ".tail(), Ok(None)); + + assert_eq!(" (foo) (bar) ".head(), Ok(Some("(foo)"))); + //assert_eq!(" (foo) (bar) ".head().head(), Ok(Some("foo"))); + //assert_eq!(" (foo) (bar) ".head().head().head(), Ok(None)); + is_some!(" (foo) (bar) ".tail(), " (bar) "); + is_some!(" (foo) (bar) ".tail().head(), "(bar)"); + is_some!(" (foo) (bar) ".tail().head().head(), "(bar)"); + is_some!(" (foo) (bar) ".tail().head().exp(), "bar"); + is_some!(" (foo) (bar) ".tail().head().exp().head(), "bar"); + + is_some!(" (foo bar baz) ".head(), "(foo bar baz)"); + is_some!(" (foo bar baz) ".head().head(), "(foo bar baz)"); + is_some!(" (foo bar baz) ".exp(), "foo bar baz"); + is_some!(" (foo bar baz) ".exp().head(), "foo"); + is_some!(" (foo bar baz) ".exp().tail(), "bar baz"); + is_some!(" (foo bar baz) ".exp().tail().head(), "bar"); + is_some!(" (foo bar baz) ".exp().tail().tail(), "baz"); + is_none!(" (foo bar baz) ".tail()); + Ok(()) +} diff --git a/input/src/lib.rs b/input/src/lib.rs index f219c64..800ee2f 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,13 +1,6 @@ #![feature(associated_type_defaults)] #![feature(if_let_guard)] - -pub(crate) use std::fmt::Debug; -pub(crate) use std::sync::Arc; -pub(crate) use std::collections::BTreeMap; -pub(crate) use std::path::{Path, PathBuf}; -pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; - mod input_macros; mod input; pub use self::input::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; diff --git a/tui/src/lib.rs b/tui/src/lib.rs index e3792f1..50d7eeb 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -1,28 +1,17 @@ #![feature(type_changing_struct_update)] - mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; - pub(crate) use ::tengri_core::*; - -pub use ::tengri_input as input; -pub(crate) use ::tengri_input::*; - -pub use ::tengri_output as output; -pub(crate) use ::tengri_output::*; - +pub use ::tengri_input as input; pub(crate) use ::tengri_input::*; +pub use ::tengri_output as output; pub(crate) use ::tengri_output::*; pub(crate) use atomic_float::AtomicF64; - pub use ::better_panic; pub(crate) use ::better_panic::{Settings, Verbosity}; - pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*}; - pub use ::crossterm; pub(crate) use ::crossterm::{ ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, }; - pub use ::ratatui; pub(crate) use ratatui::{ prelude::{Color, Style, Buffer}, style::Modifier, @@ -30,10 +19,8 @@ pub use ::ratatui; pub(crate) use ratatui::{ layout::{Size, Rect}, buffer::Cell }; - pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; pub(crate) use std::io::{stdout, Stdout}; - #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { use crate::*; //use std::sync::{Arc, RwLock}; @@ -55,7 +42,6 @@ pub(crate) use std::io::{stdout, Stdout}; //engine.run(&state)?; Ok(()) } - #[cfg(test)] #[test] fn test_parse_key () { //use KeyModifiers as Mods; let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y))); From a601d3d80619eca1e8ca956c649aa7edb7d974ff Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 3 Aug 2025 01:58:50 +0300 Subject: [PATCH 130/178] so many ood tests --- input/src/input_dsl.rs | 20 +----- input/src/input_test.rs | 8 +-- tengri/src/test.rs | 155 ++++++++++++++++++++-------------------- 3 files changed, 84 insertions(+), 99 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index bb3c353..1f6ac42 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,10 +1,9 @@ use crate::*; - +use std::{sync::Arc, collections::BTreeMap, path::{Path, PathBuf}, fs::{exists, read_to_string}}; /// Map of each event (e.g. key combination) to /// all command expressions bound to it by /// all loaded input layers. type EventMapImpl = BTreeMap>>; - /// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation @@ -16,7 +15,6 @@ type EventMapImpl = BTreeMap>>; /// that .event()binding's value is returned. #[derive(Debug)] pub struct EventMap(EventMapImpl); - /// An input binding. #[derive(Debug, Clone)] pub struct Binding { @@ -25,18 +23,14 @@ pub struct Binding { pub description: Option>, pub source: Option>, } - /// Input bindings are only returned if this evaluates to true #[derive(Clone)] pub struct Condition(Arcbool + Send + Sync>>); - impl_debug!(Condition |self, w| { write!(w, "*") }); - /// Default is always empty map regardless if `E` and `C` implement [Default]. impl Default for EventMap { fn default () -> Self { Self(Default::default()) } } - impl EventMap { /// Create a new event map pub fn new () -> Self { @@ -68,7 +62,7 @@ impl EventMap { /// Create event map from path to text file. pub fn from_path > (path: P) -> Usually where E: From> { if exists(path.as_ref())? { - Self::from_source(read_and_leak(path)?) + Self::from_source(read_to_string(path)?) } else { return Err(format!("(e5) not found: {:?}", path.as_ref()).into()) } @@ -117,7 +111,6 @@ impl EventMap { Ok(map) } } - impl Binding { fn from_dsl (dsl: impl Dsl) -> Usually { let mut command: Option = None; @@ -131,18 +124,9 @@ impl Binding { } } } - fn unquote (x: &str) -> &str { let mut chars = x.chars(); chars.next(); //chars.next_back(); chars.as_str() } - -fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { - Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) -} - -fn leak (x: impl AsRef) -> &'static str { - Box::leak(x.as_ref().into()) -} diff --git a/input/src/input_test.rs b/input/src/input_test.rs index 8373435..24576db 100644 --- a/input/src/input_test.rs +++ b/input/src/input_test.rs @@ -21,8 +21,8 @@ use crate::*; Ok(()) } -#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { - let _keymap = CstIter::new(""); - Ok(()) -} +//#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { + //let _keymap = CstIter::new(""); + //Ok(()) +//} diff --git a/tengri/src/test.rs b/tengri/src/test.rs index c782996..2df3c8a 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,86 +1,87 @@ -use crate::*; -use crate::{dsl::*, input::*, tui::TuiIn}; -use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; -use std::cmp::Ordering; +// FIXME +//use crate::*; +//use crate::{dsl::*, input::*, tui::TuiIn}; +//use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; +//use std::cmp::Ordering; -#[test] fn test_subcommand () -> Usually<()> { - #[derive(Debug)] struct Event(crossterm::event::Event); - impl Eq for Event {} - impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } - impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } - impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } - struct Test { keys: InputMap } +//#[test] fn test_subcommand () -> Usually<()> { + //#[derive(Debug)] struct Event(crossterm::event::Event); + //impl Eq for Event {} + //impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } + //impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } + //impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } + //struct Test { keys: InputMap } - handle!(TuiIn: |self: Test, input|Ok(None));/*if let Some(command) = self.keys.command(self, input) { - Ok(Some(true)) - } else { - Ok(None) - });*/ + //handle!(TuiIn: |self: Test, input|Ok(None));[>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 { - Ok(None) - } - fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { - Ok(None) - } - fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { - Ok(command.execute(state)?.map(|command|Self::DoSub { command })) - } - } + //#[tengri_proc::command(Test)] + //impl TestCommand { + //fn do_thing (_state: &mut Test) -> Perhaps { + //Ok(None) + //} + //fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + //Ok(None) + //} + //fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { + //Ok(command.execute(state)?.map(|command|Self::DoSub { command })) + //} + //} - #[tengri_proc::command(Test)] - impl TestSubcommand { - fn do_other_thing (_state: &mut Test) -> Perhaps { - Ok(None) - } - fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { - Ok(None) - } - } + //#[tengri_proc::command(Test)] + //impl TestSubcommand { + //fn do_other_thing (_state: &mut Test) -> Perhaps { + //Ok(None) + //} + //fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + //Ok(None) + //} + //} - let mut test = Test { - keys: InputMap::from_source(" - (@a do-thing) - (@b do-thing-arg 0) - (@c do-sub do-other-thing) - (@d do-sub do-other-thing-arg 0) - ")? - }; + //let mut test = Test { + //keys: InputMap::from_source(" + //(@a do-thing) + //(@b do-thing-arg 0) + //(@c do-sub do-other-thing) + //(@d do-sub do-other-thing-arg 0) + //")? + //}; - //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - //kind: KeyEventKind::Press, - //code: KeyCode::Char('a'), - //modifiers: KeyModifiers::NONE, - //state: KeyEventState::NONE, - //})))?); - //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - //kind: KeyEventKind::Press, - //code: KeyCode::Char('b'), - //modifiers: KeyModifiers::NONE, - //state: KeyEventState::NONE, - //})))?); - //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - //kind: KeyEventKind::Press, - //code: KeyCode::Char('c'), - //modifiers: KeyModifiers::NONE, - //state: KeyEventState::NONE, - //})))?); - //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - //kind: KeyEventKind::Press, - //code: KeyCode::Char('d'), - //modifiers: KeyModifiers::NONE, - //state: KeyEventState::NONE, - //})))?); - //assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - //kind: KeyEventKind::Press, - //code: KeyCode::Char('z'), - //modifiers: KeyModifiers::NONE, - //state: KeyEventState::NONE, - //})))?); - Ok(()) -} + ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + ////kind: KeyEventKind::Press, + ////code: KeyCode::Char('a'), + ////modifiers: KeyModifiers::NONE, + ////state: KeyEventState::NONE, + ////})))?); + ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + ////kind: KeyEventKind::Press, + ////code: KeyCode::Char('b'), + ////modifiers: KeyModifiers::NONE, + ////state: KeyEventState::NONE, + ////})))?); + ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + ////kind: KeyEventKind::Press, + ////code: KeyCode::Char('c'), + ////modifiers: KeyModifiers::NONE, + ////state: KeyEventState::NONE, + ////})))?); + ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + ////kind: KeyEventKind::Press, + ////code: KeyCode::Char('d'), + ////modifiers: KeyModifiers::NONE, + ////state: KeyEventState::NONE, + ////})))?); + ////assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + ////kind: KeyEventKind::Press, + ////code: KeyCode::Char('z'), + ////modifiers: KeyModifiers::NONE, + ////state: KeyEventState::NONE, + ////})))?); + //Ok(()) +//} //FIXME: //#[cfg(test)] #[test] fn test_dsl_context () { From 9ccd7e5c69f6f8aa5ab43f22d5953d56427d4f14 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 3 Aug 2025 19:36:01 +0300 Subject: [PATCH 131/178] dsl: macro dsl_for_each -> method each --- dsl/src/dsl.rs | 22 ++++++++-------------- input/src/input.rs | 4 ---- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index c43e2f1..b2bdf99 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -28,6 +28,14 @@ impl DslExp for D {} pub trait DslExp: Dsl { fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail(self.head())))} + fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> { + Ok(if let Some(head) = self.head()? { + cb(head)?; + if let Some(tail) = self.tail()? { + tail.each(cb)?; + } + }) + } } impl DslText for D {} pub trait DslText: Dsl { fn text (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(text_peek_only))} @@ -55,20 +63,6 @@ pub enum DslError { #[error("parse failed: error #{0}")] Code(u8), #[error("end reached")] End } -#[macro_export] macro_rules! dsl_for_each (($dsl:expr => |$next:ident|$body:expr)=>{ - let mut dsl: Arc = $dsl.src().into(); - let mut $next: Option> = dsl.next()?.map(Into::into); - let mut rest: Option> = dsl.rest()?.map(Into::into); - loop { - if let Some($next) = $next { $body } else { break }; - if let Some(next) = rest { - $next = next.next()?.map(Into::into); - rest = next.rest()?.map(Into::into); - } else { - break - } - } -}); fn ok_flat (x: Option>) -> DslPerhaps { Ok(x.transpose()?.flatten()) } fn peek_tail <'a> (head: DslPerhaps<&'a str>) -> impl Fn(&'a str)->DslPerhaps<&'a str> { move|src|match head { diff --git a/input/src/input.rs b/input/src/input.rs index 7b451ee..48e692f 100644 --- a/input/src/input.rs +++ b/input/src/input.rs @@ -1,5 +1,4 @@ use crate::*; - /// Event source pub trait Input: Sized { /// Type of input event @@ -13,13 +12,11 @@ pub trait Input: Sized { /// Mark component as done fn done (&self); } - flex_trait_mut!(Handle { fn handle (&mut self, _input: &E) -> Perhaps { Ok(None) } }); - pub trait Command: Send + Sync + Sized { fn execute (self, state: &mut S) -> Perhaps; fn delegate (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps @@ -28,7 +25,6 @@ pub trait Command: Send + Sync + Sized { Ok(self.execute(state)?.map(wrap)) } } - impl> Command for Option { fn execute (self, _: &mut S) -> Perhaps { Ok(None) From 104bb1c8e76cacf249ccd340712ea7bd2d33b5f6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 3 Aug 2025 21:22:55 +0300 Subject: [PATCH 132/178] more dsl runaround --- dsl/src/dsl.rs | 168 +++++++++++++++++++++----------------------- dsl/src/dsl_test.rs | 2 + 2 files changed, 83 insertions(+), 87 deletions(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index b2bdf99..c6c4747 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -13,6 +13,11 @@ pub(crate) use ::tengri_core::*; pub(crate) use self::DslError::*; mod dsl_conv; pub use self::dsl_conv::*; #[cfg(test)] mod dsl_test; +/// DSL-specific result type. +pub type DslResult = Result; +/// DSL-specific optional result type. +pub type DslPerhaps = Result, DslError>; +// Designates a string as parsable DSL. flex_trait!(Dsl: Debug + Send + Sync + Sized { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } }); @@ -24,19 +29,6 @@ impl Dsl for Option { impl Dsl for Result { fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}} } -impl DslExp for D {} pub trait DslExp: Dsl { - fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} - fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} - fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail(self.head())))} - fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> { - Ok(if let Some(head) = self.head()? { - cb(head)?; - if let Some(tail) = self.tail()? { - tail.each(cb)?; - } - }) - } -} impl DslText for D {} pub trait DslText: Dsl { fn text (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(text_peek_only))} } @@ -49,30 +41,72 @@ impl DslKey for D {} pub trait DslKey: Dsl { impl DslNum for D {} pub trait DslNum: Dsl { fn num (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(num_peek_only))} } -/// DSL-specific result type. -pub type DslResult = Result; -/// DSL-specific optional result type. -pub type DslPerhaps = Result, DslError>; +impl DslExp for D {} pub trait DslExp: Dsl { + fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} + fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} + fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))} + fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> { + Ok(if let Some(head) = self.head()? { + println!("| HEAD ->\n{head}"); + cb(head)?; + if let Some(tail) = self.tail()? { + println!("| TAIL ->\n{tail}"); + tail.each(cb)?; + } + }) + } +} +pub const fn peek (src: &str) -> DslPerhaps<&str> { + Ok(Some(if let Ok(Some(exp)) = exp_peek(src) { exp } else + if let Ok(Some(sym)) = sym_peek(src) { sym } else + if let Ok(Some(key)) = key_peek(src) { key } else + if let Ok(Some(num)) = num_peek(src) { num } else + if let Ok(Some(text)) = text_peek(src) { text } else + if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) } + else { return Ok(None) })) +} +pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { + Ok(Some(if let Ok(Some(exp)) = exp_seek(src) { exp } else + if let Ok(Some(sym)) = sym_seek(src) { sym } else + if let Ok(Some(key)) = key_seek(src) { key } else + if let Ok(Some(num)) = num_seek(src) { num } else + if let Ok(Some(text)) = text_seek(src) { text } else + if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) } + else { return Ok(None) })) +} +pub const fn peek_tail (src: &str) -> DslPerhaps<&str> { + match seek(src) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => { + let tail = str_range(src, start + length, src.len()); + for_each!((i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) }); + Ok(None) + }, + } +} /// DSL-specific error codes. #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] pub enum DslError { #[error("parse failed: not implemented")] Unimplemented, #[error("parse failed: empty")] Empty, #[error("parse failed: incomplete")] Incomplete, - #[error("parse failed: unexpected character '{0}'")] Unexpected(char), + #[error("parse failed: unexpected character '{0}'")] Unexpected(char, Option, Option<&'static str>), #[error("parse failed: error #{0}")] Code(u8), #[error("end reached")] End } fn ok_flat (x: Option>) -> DslPerhaps { Ok(x.transpose()?.flatten()) } -fn peek_tail <'a> (head: DslPerhaps<&'a str>) -> impl Fn(&'a str)->DslPerhaps<&'a str> { - move|src|match head { - Ok(Some(next)) => { - let src = &src[src.len().min(1 + next.len())..]; - for c in src.chars() { if !is_whitespace(c) { return Ok(Some(src)) } } - Ok(None) - }, - e => e - } +pub const fn is_space (c: char) -> bool { + matches!(c, ' '|'\n'|'\r'|'\t') +} +pub const fn no_trailing_non_space (source: &str, offset: usize, context: Option<&'static str>) -> DslResult<()> { + Ok(for_each!((i, c) in char_indices(str_range(source, offset, source.len())) => if !is_space(c) { + return Err(Unexpected(c, Some(offset + i), if let Some(context) = context { + Some(context) + } else { + Some("trailing non-space") + })) + })) } macro_rules! def_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ /// Find a slice corrensponding to a syntax token. @@ -91,7 +125,7 @@ macro_rules! def_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_st Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => { - if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } + if let Err(e) = no_trailing_non_space(source, start + length, Some("peek_only")) { return Err(e) } Ok(Some(str_range(source, start, start + length))) } } @@ -125,7 +159,7 @@ pub const fn exp_peek_inner_only (source: &str) -> DslPerhaps<&str> { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => { - if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } + if let Err(e) = no_trailing_non_space(source, start + length, Some("exp_peek_inner_only")) { return Err(e) } let peeked = str_range(source, start, start + length); let len = peeked.len(); let start = if len > 0 { 1 } else { 0 }; @@ -138,8 +172,8 @@ pub const fn is_exp_end (c: char) -> bool { c == ')' } pub const fn exp_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_exp_start(c) { return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) + } else if !is_space(c) { + return Err(Unexpected(c, Some(i), Some("expected expression start"))) }); Ok(None) } @@ -149,7 +183,7 @@ pub const fn exp_seek_length (source: &str) -> DslPerhaps { depth += 1; } else if is_exp_end(c) { if depth == 0 { - return Err(Unexpected(c)) + return Err(Unexpected(c, Some(i), Some("expected expression end"))) } else if depth == 1 { return Ok(Some(i + 1)) } else { @@ -160,13 +194,13 @@ pub const fn exp_seek_length (source: &str) -> DslPerhaps { } def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } -pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn is_sym_char (c: char) -> bool { matches!(c, ':'|'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } +pub const fn is_sym_end (c: char) -> bool { is_space(c) || matches!(c, ')') } pub const fn sym_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_sym_start(c) { return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) + } else if !is_space(c) { + return Err(Unexpected(c, Some(i), Some("sym_seek_start"))) }); Ok(None) } @@ -174,19 +208,19 @@ pub const fn sym_seek_length (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_sym_end(c) { return Ok(Some(i)) } else if !is_sym_char(c) { - return Err(Unexpected(c)) + return Err(Unexpected(c, Some(i), Some("sym_seek_length"))) }); Ok(Some(source.len())) } def_peek_seek!(key_peek, key_peek_only, key_seek, key_seek_start, key_seek_length); pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|('a'..='z')) } pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } -pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn is_key_end (c: char) -> bool { is_space(c) || matches!(c, ')') } pub const fn key_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_key_start(c) { return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) + } else if !is_space(c) { + return Err(Unexpected(c, Some(i), None)) }); Ok(None) } @@ -194,7 +228,7 @@ pub const fn key_seek_length (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_key_end(c) { return Ok(Some(i)) } else if !is_key_char(c) { - return Err(Unexpected(c)) + return Err(Unexpected(c, Some(i), None)) }); Ok(Some(source.len())) } @@ -204,8 +238,8 @@ pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } pub const fn text_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_text_start(c) { return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) + } else if !is_space(c) { + return Err(Unexpected(c, Some(i), None)) }); Ok(None) } @@ -217,8 +251,8 @@ def_peek_seek!(num_peek, num_peek_only, num_seek, num_seek_start, num_seek_lengt pub const fn num_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_digit(c) { return Ok(Some(i)); - } else if !is_whitespace(c) { - return Err(Unexpected(c)) + } else if !is_space(c) { + return Err(Unexpected(c, Some(i), None)) }); Ok(None) } @@ -226,7 +260,7 @@ pub const fn num_seek_length (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_num_end(c) { return Ok(Some(i)) } else if !is_digit(c) { - return Err(Unexpected(c)) + return Err(Unexpected(c, Some(i), None)) }); Ok(None) } @@ -248,46 +282,6 @@ pub const fn to_digit (c: char) -> Result { Ok(match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Err(Unexpected(c)) + _ => return Err(Unexpected(c, None, Some("parse digit"))) }) } -pub const fn peek (src: &str) -> DslPerhaps<&str> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_peek(src) => exp, - _ if let Ok(Some(sym)) = sym_peek(src) => sym, - _ if let Ok(Some(key)) = key_peek(src) => key, - _ if let Ok(Some(num)) = num_peek(src) => num, - _ if let Ok(Some(text)) = text_peek(src) => text, - _ => { - for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} -pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_seek(src) => exp, - _ if let Ok(Some(sym)) = sym_seek(src) => sym, - _ if let Ok(Some(key)) = key_seek(src) => key, - _ if let Ok(Some(num)) = num_seek(src) => num, - _ if let Ok(Some(text)) = text_seek(src) => text, - _ => { - for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} -pub const fn is_whitespace (c: char) -> bool { - matches!(c, ' '|'\n'|'\r'|'\t') -} -pub const fn no_trailing_non_whitespace (source: &str, offset: usize) -> DslResult<()> { - let tail = str_range(source, offset, source.len()); - for_each!((_, c) in char_indices(tail) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(()) -} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs index 05f0308..253b4c9 100644 --- a/dsl/src/dsl_test.rs +++ b/dsl/src/dsl_test.rs @@ -21,6 +21,8 @@ macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())}; check("(a b c) d e f", Err(e1), Err(e3), Ok(Some("(a b c)")), Ok(Some("d e f"))); check("a (b c d) e f", Err(e1), Err(e0), Ok(Some("a")), Ok(Some("(b c d) e f"))); + is_some!(sym_peek("\n :view/transport"), ":view/transport"); + assert!(is_whitespace(' ')); assert!(!is_key_start(' ')); assert!(is_key_start('f')); From b52c1f582880d08e663411e9238d3fdacfda9473 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 4 Aug 2025 15:17:46 +0300 Subject: [PATCH 133/178] tui: add ErrorBoundary component --- tui/src/tui_content.rs | 3 ++- tui/src/tui_content/tui_error.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tui/src/tui_content/tui_error.rs diff --git a/tui/src/tui_content.rs b/tui/src/tui_content.rs index 108d0b2..9fc0c13 100644 --- a/tui/src/tui_content.rs +++ b/tui/src/tui_content.rs @@ -15,10 +15,11 @@ macro_rules! impl_content_layout_render { mod tui_border; pub use self::tui_border::*; mod tui_button; //pub use self::tui_button::*; mod tui_color; pub use self::tui_color::*; +mod tui_error; pub use self::tui_error::*; mod tui_field; pub use self::tui_field::*; +mod tui_number; //pub use self::tui_number::*; mod tui_phat; pub use self::tui_phat::*; mod tui_repeat; pub use self::tui_repeat::*; -mod tui_number; //pub use self::tui_number::*; mod tui_scroll; pub use self::tui_scroll::*; mod tui_string; pub use self::tui_string::*; mod tui_style; pub use self::tui_style::*; diff --git a/tui/src/tui_content/tui_error.rs b/tui/src/tui_content/tui_error.rs new file mode 100644 index 0000000..e48f203 --- /dev/null +++ b/tui/src/tui_content/tui_error.rs @@ -0,0 +1,24 @@ +use crate::*; +use ratatui::style::Stylize; + +// Thunks can be natural error boundaries! +pub struct ErrorBoundary>(std::marker::PhantomData, Perhaps); + +impl> ErrorBoundary { + pub fn new (content: Perhaps) -> Self { + Self(Default::default(), content) + } +} + +impl> Content for ErrorBoundary { + fn content (&self) -> impl Render + '_ { + ThunkRender::new(|to|match self.1.as_ref() { + Ok(Some(content)) => content.render(to), + Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())), + Err(e) => Content::render(&Tui::fg_bg( + Color::Rgb(255,224,244), Color::Rgb(96,24,24), Bsp::s( + Bsp::e(Tui::bold(true, "oops. "), "rendering failed."), + Bsp::e("\"why?\" ", Tui::bold(true, &format!("{e}"))))), to) + }) + } +} From 24ac52d8079140b2b9f363d1568597bb5600a166 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 01:14:26 +0300 Subject: [PATCH 134/178] tui: keybinds work? --- dsl/src/dsl.rs | 11 +-- dsl/src/dsl_conv.rs | 38 ++++---- input/src/input_dsl.rs | 94 ++++++++++---------- output/src/output.rs | 2 +- proc/src/proc_view.rs | 24 ++--- tui/src/lib.rs | 1 + tui/src/tui_engine.rs | 1 + tui/src/tui_engine/tui_event.rs | 147 +++++++++++++++++++++++++++++++ tui/src/tui_engine/tui_input.rs | 121 +------------------------ tui/src/tui_engine/tui_output.rs | 2 +- 10 files changed, 238 insertions(+), 203 deletions(-) create mode 100644 tui/src/tui_engine/tui_event.rs diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index c6c4747..e9d108b 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -17,12 +17,14 @@ mod dsl_conv; pub use self::dsl_conv::*; pub type DslResult = Result; /// DSL-specific optional result type. pub type DslPerhaps = Result, DslError>; +// Some things that can be DSL source: +impl Dsl for String { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } // Designates a string as parsable DSL. flex_trait!(Dsl: Debug + Send + Sync + Sized { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } }); -impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } -impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } impl Dsl for Option { fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} } @@ -45,12 +47,11 @@ impl DslExp for D {} pub trait DslExp: Dsl { fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))} + /// my other car is a cdr :< fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> { Ok(if let Some(head) = self.head()? { - println!("| HEAD ->\n{head}"); cb(head)?; if let Some(tail) = self.tail()? { - println!("| TAIL ->\n{tail}"); tail.each(cb)?; } }) @@ -194,7 +195,7 @@ pub const fn exp_seek_length (source: &str) -> DslPerhaps { } def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -pub const fn is_sym_char (c: char) -> bool { matches!(c, ':'|'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } +pub const fn is_sym_char (c: char) -> bool { is_sym_start(c) || matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } pub const fn is_sym_end (c: char) -> bool { is_space(c) || matches!(c, ')') } pub const fn sym_seek_start (source: &str) -> DslPerhaps { for_each!((i, c) in char_indices(source) => if is_sym_start(c) { diff --git a/dsl/src/dsl_conv.rs b/dsl/src/dsl_conv.rs index 0600679..ae214dd 100644 --- a/dsl/src/dsl_conv.rs +++ b/dsl/src/dsl_conv.rs @@ -12,24 +12,24 @@ pub trait FromDsl: Sized { } /// `self` + [Dsl] -> `T` -pub trait DslInto<'s, T> { - fn dsl_into (&'s self, dsl: &impl Dsl) -> Perhaps; - fn dsl_into_or (&'s self, dsl: &impl Dsl, err: Box) -> Usually { +pub trait DslInto { + fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; + fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { self.dsl_into(dsl)?.ok_or(err) } - fn dsl_into_or_else (&'s self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { + fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { self.dsl_into(dsl)?.ok_or_else(err) } } /// `self` + `T` -> [Dsl] pub trait DslFrom { - fn dsl_from (&self, dsl: &T) -> Perhaps; - fn dsl_from_or (&self, dsl: &T, err: Box) -> Usually { - self.dsl_from(dsl)?.ok_or(err) + fn dsl_from (&self, val: &T) -> Perhaps; + fn dsl_from_or (&self, val: &T, err: Box) -> Usually { + self.dsl_from(val)?.ok_or(err) } - fn dsl_from_or_else (&self, dsl: &T, err: impl Fn()->Box) -> Usually { - self.dsl_from(dsl)?.ok_or_else(err) + fn dsl_from_or_else (&self, val: &T, err: impl Fn()->Box) -> Usually { + self.dsl_from(val)?.ok_or_else(err) } } @@ -44,14 +44,14 @@ pub trait IntoDsl { } } -impl<'s, T: FromDsl, U> DslInto<'s, T> for U { - fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { - T::from_dsl(self, dsl) - } -} +//impl<'s, T: FromDsl, U> DslInto<'s, T> for U { + //fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps { + //T::from_dsl(self, dsl) + //} +//} -impl, U> IntoDsl for U { - fn into_dsl (&self, state: &T) -> Perhaps { - T::dsl_from(state, self) - } -} +//impl, U> IntoDsl for U { + //fn into_dsl (&self, state: &T) -> Perhaps { + //T::dsl_from(state, self) + //} +//} diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 1f6ac42..cc54282 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -14,7 +14,7 @@ type EventMapImpl = BTreeMap>>; /// When the first non-conditional or true conditional binding is executed, /// that .event()binding's value is returned. #[derive(Debug)] -pub struct EventMap(EventMapImpl); +pub struct EventMap(pub EventMapImpl); /// An input binding. #[derive(Debug, Clone)] pub struct Binding { @@ -60,59 +60,63 @@ impl EventMap { .flatten() } /// Create event map from path to text file. - pub fn from_path > (path: P) -> Usually where E: From> { + pub fn load_from_path <'s> ( + &'s mut self, path: impl AsRef + ) -> Usually<&mut Self> where Self: DslInto + DslInto { if exists(path.as_ref())? { - Self::from_source(read_to_string(path)?) + let source = read_to_string(&path)?; + let path: Arc = Arc::new(path.as_ref().into()); + self.load_from_source(&source, &Some(&path)) } else { return Err(format!("(e5) not found: {:?}", path.as_ref()).into()) } } - /// Create event map from string. - pub fn from_source (source: impl AsRef) -> Usually where E: From> { - Self::from_dsl(&mut source.as_ref()) - } /// Create event map from DSL tokenizer. - pub fn from_dsl <'s, > (dsl: &'s mut impl Dsl) -> Usually where E: From> { - let mut map: Self = Default::default(); - if let Some(dsl) = dsl.exp()? { - let mut head = dsl.head()?; - let mut tail = dsl.tail()?; - loop { - if let Some(ref token) = head { - if let Some(ref text) = token.text()? { - map.0.extend(Self::from_path(PathBuf::from(text))?.0); - continue - } - //if let Some(ref exp) = token.exp()? { - ////_ if let Some(sym) = token.head()?.sym()?.as_ref() => { - ////todo!() - ////}, - ////_ if Some(&"if") == token.head()?.key()?.as_ref() => { - ////todo!() - ////}, - //return Err(format!("unexpected: {:?}", token.exp()).into()) - //} - return Err(format!("unexpected: {token:?}").into()) - } else { - break - } - if let Some(next) = tail { - head = next.head()?; - tail = next.tail()?; - } else { - break - } - } - } else if let Some(text) = dsl.text()? { - todo!("load from file path") - } else { - todo!("return error for invalid input") - } - Ok(map) + pub fn load_from_source ( + &mut self, dsl: impl Dsl, path: &Option<&Arc> + ) -> Usually<&mut Self> where Self: DslInto + DslInto { + dsl.each(|dsl|self.load_from_source_one(&dsl, path).map(move|_|()))?; + Ok(self) } + /// Load one event binding into the event map. + pub fn load_from_source_one <'s> ( + &'s mut self, dsl: impl Dsl, path: &Option<&Arc> + ) -> Usually<&mut Self> where Self: DslInto + DslInto { + if let Some(exp) = dsl.head()?.exp()? + && let Some(sym) = exp.head()?.sym()? + && let Some(tail) = exp.tail()? + { + let event = self.dsl_into_or_else(&sym, ||panic!())?; + let command = self.dsl_into_or_else(&tail, ||panic!())?; + Ok(self.add(event, Binding { + command, condition: None, description: None, source: path.cloned() + })) + } else { + Err(format!("unexpected: {:?}", dsl.head()?).into()) + } + } + //})Ok(if let Some(sym) = dsl.head()?.exp()?.head()?.sym()? { + //if let Some(tail) = dsl.head()?.exp()?.tail()? { + //let event: E = sym.into(); + //let binding: Binding = Binding { command: tail.into(), condition: None, description: None, source: None }; + //if let Some(bindings) = map.0.get_mut(&event) { + //bindings.push(binding); + //} else { + //map.0.insert(event, vec![binding]); + //} + //} else { + //panic!("empty binding: {}", dsl.head()?.exp()?.unwrap_or_default()) + //} + //} else if let Some(ref text) = dsl.text()? { + //map.0.extend(Self::from_path(PathBuf::from(text))?.0); + //} else { + //return Err(format!("unexpected: {dsl:?}").into()) + //})); + //Ok(map) + //} } impl Binding { - fn from_dsl (dsl: impl Dsl) -> Usually { + pub fn from_dsl (dsl: impl Dsl) -> Usually { let mut command: Option = None; let mut condition: Option = None; let mut description: Option> = None; diff --git a/output/src/output.rs b/output/src/output.rs index 07c8ac2..77a48b8 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -14,7 +14,7 @@ pub trait Output: Send + Sync + Sized { /// Mutable pointer to area fn area_mut (&mut self) -> &mut Self::Area; /// Render widget in area - fn place + ?Sized> (&mut self, area: Self::Area, content: &T); + fn place <'t, T: Render + ?Sized> (&mut self, area: Self::Area, content: &'t T); #[inline] fn x (&self) -> Self::Unit { self.area().x() } #[inline] fn y (&self) -> Self::Unit { self.area().y() } #[inline] fn w (&self) -> Self::Unit { self.area().w() } diff --git a/proc/src/proc_view.rs b/proc/src/proc_view.rs index 13a5d5c..7a70b2e 100644 --- a/proc/src/proc_view.rs +++ b/proc/src/proc_view.rs @@ -7,25 +7,26 @@ pub(crate) struct ViewDef(pub(crate) ViewMeta, pub(crate) ViewImpl); pub(crate) struct ViewMeta { pub(crate) output: Ident } #[derive(Debug, Clone)] -pub(crate) struct ViewImpl { - block: ItemImpl, - exposed: BTreeMap, -} +pub(crate) struct ViewImpl { block: ItemImpl, exposed: BTreeMap, } +/// `#[view]` takes 1 arg ([Engine] implementation for which it's valid). impl Parse for ViewMeta { fn parse (input: ParseStream) -> Result { - Ok(Self { - output: input.parse::()?, - }) + Ok(Self { output: input.parse::()?, }) } } +/// `#[view]` exposes each function as corresponding symbol. +/// +/// Maybe it's best to genericize that pattern rather than have +/// 3 whole things for the layers of the program. impl Parse for ViewImpl { fn parse (input: ParseStream) -> Result { let block = input.parse::()?; let mut exposed: BTreeMap = Default::default(); for item in block.items.iter() { - if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, .. }, .. }) = item { + if let ImplItem::Fn(ImplItemFn { sig: Signature { ident, inputs, .. }, .. }) = item + && inputs.len() == 1 { let key = format!(":{}", AsKebabCase(format!("{}", &ident))); if exposed.contains_key(&key) { return Err(input.error(format!("already defined: {ident}"))); @@ -37,6 +38,7 @@ impl Parse for ViewImpl { } } +/// `#[view]`'s output is a generated block of symbol bindings. impl ToTokens for ViewDef { fn to_tokens (&self, out: &mut TokenStream2) { let Self(_, ViewImpl { block, .. }) = self; @@ -78,10 +80,8 @@ impl 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::DslInto<'state, - Box + 'state> - > for #self_ty { - fn dsl_into (&'state self, dsl: &impl ::tengri::dsl::Dsl) + impl<'state> ::tengri::dsl::DslInto + 'state>> for #self_ty { + fn dsl_into (&self, dsl: &impl ::tengri::dsl::Dsl) -> Perhaps + 'state>> { #(#builtins)* diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 50d7eeb..8ed9006 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -2,6 +2,7 @@ mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; pub(crate) use ::tengri_core::*; +#[cfg(feature = "dsl")] pub use ::tengri_dsl::*; pub use ::tengri_input as input; pub(crate) use ::tengri_input::*; pub use ::tengri_output as output; pub(crate) use ::tengri_output::*; pub(crate) use atomic_float::AtomicF64; diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 9cd527d..4700555 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -3,6 +3,7 @@ use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; +mod tui_event; pub use self::tui_event::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; diff --git a/tui/src/tui_engine/tui_event.rs b/tui/src/tui_engine/tui_event.rs new file mode 100644 index 0000000..93dd4ff --- /dev/null +++ b/tui/src/tui_engine/tui_event.rs @@ -0,0 +1,147 @@ +use crate::*; +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(Event); +impl Ord for TuiEvent { + fn cmp (&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other) + .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf + } +} +impl TuiEvent { + pub fn from_crossterm (event: Event) -> Self { + Self(event) + } + pub fn from_dsl (dsl: impl Dsl) -> Perhaps { + Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self)) + } +} +pub struct TuiKey(Option, KeyModifiers); +impl TuiKey { + const SPLIT: char = '/'; + pub fn from_dsl (dsl: impl Dsl) -> Usually { + if let Some(symbol) = dsl.sym()? { + let symbol = symbol.trim(); + Ok(if symbol == ":char" { + Self(None, KeyModifiers::NONE) + } else if symbol.chars().nth(0) == Some('@') { + let mut key = None; + let mut modifiers = KeyModifiers::NONE; + let mut tokens = symbol[1..].split(Self::SPLIT).peekable(); + while let Some(token) = tokens.next() { + println!("{token}"); + if tokens.peek().is_some() { + match token { + "ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL, + "alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT, + "shift" | "Shift" | "s" | "S" => { + modifiers |= KeyModifiers::SHIFT; + // + TODO normalize character case, BackTab, etc. + }, + _ => panic!("unknown modifier {token}"), + } + } else { + key = if token.len() == 1 { + Some(KeyCode::Char(token.chars().next().unwrap())) + } else { + Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + } + } + } + Self(key, modifiers) + } else { + return Err(format!("TuiKey: unexpected: {symbol}").into()) + }) + } else { + return Err(format!("TuiKey: unspecified").into()) + } + } + pub fn to_crossterm (&self) -> Option { + self.0.map(|code|Event::Key(KeyEvent { + code, + modifiers: self.1, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + })) + } +} +pub fn named_key (token: &str) -> Option { + use KeyCode::*; + Some(match token { + "up" => Up, + "down" => Down, + "left" => Left, + "right" => Right, + "esc" | "escape" => Esc, + "enter" | "return" => Enter, + "delete" | "del" => Delete, + "backspace" => Backspace, + "tab" => Tab, + "space" => Char(' '), + "comma" => Char(','), + "period" => Char('.'), + "plus" => Char('+'), + "minus" | "dash" => Char('-'), + "equal" | "equals" => Char('='), + "underscore" => Char('_'), + "backtick" => Char('`'), + "lt" => Char('<'), + "gt" => Char('>'), + "cbopen" | "openbrace" => Char('{'), + "cbclose" | "closebrace" => Char('}'), + "bropen" | "openbracket" => Char('['), + "brclose" | "closebracket" => Char(']'), + "pgup" | "pageup" => PageUp, + "pgdn" | "pagedown" => PageDown, + "f1" => F(1), + "f2" => F(2), + "f3" => F(3), + "f4" => F(4), + "f5" => F(5), + "f6" => F(6), + "f7" => F(7), + "f8" => F(8), + "f9" => F(9), + "f10" => F(10), + "f11" => F(11), + "f12" => F(12), + _ => return None, + }) +} + //let token = token.as_ref(); + //if token.len() < 2 { + //Self { valid: false, key: None, mods: KeyModifiers::NONE } + //} else if token.chars().next() != Some('@') { + //Self { valid: false, key: None, mods: KeyModifiers::NONE } + //} else { + //Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) + //} + //} + //pub fn build (self) -> Option { + //if self.valid && self.key.is_some() { + //Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) + //} else { + //None + //} + //} + //fn next (mut self, token: &str) -> Self { + //let mut tokens = token.split('-').peekable(); + //while let Some(token) = tokens.next() { + //if tokens.peek().is_some() { + //match token { + //"ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, + //"alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, + //"shift" | "Shift" | "s" | "S" => { + //self.mods |= KeyModifiers::SHIFT; + //// + TODO normalize character case, BackTab, etc. + //}, + //_ => panic!("unknown modifier {token}"), + //} + //} else { + //self.key = if token.len() == 1 { + //Some(KeyCode::Char(token.chars().next().unwrap())) + //} else { + //Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) + //} + //} + //} + //self + //} diff --git a/tui/src/tui_engine/tui_input.rs b/tui/src/tui_engine/tui_input.rs index 1d89f97..f42da7c 100644 --- a/tui/src/tui_engine/tui_input.rs +++ b/tui/src/tui_engine/tui_input.rs @@ -17,23 +17,6 @@ impl Input for TuiIn { fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) } fn done (&self) { self.exited.store(true, Relaxed); } } -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] -pub struct TuiEvent(Event); -impl Ord for TuiEvent { - fn cmp (&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other) .unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf - } -} -impl From for TuiEvent { - fn from (event: Event) -> Self { - Self(event) - } -} -impl From> for TuiEvent { - fn from (x: Arc) -> Self { - TuiEvent(TuiKey::new(x.as_ref()).build().unwrap_or_else(||panic!("invalid key: {x}"))) - } -} impl TuiIn { /// Spawn the input thread. pub fn run_input + Send + Sync + 'static> ( @@ -60,7 +43,7 @@ impl TuiIn { }, _ => { let exited = exited.clone(); - let event = event.into(); + let event = TuiEvent::from_crossterm(event); if let Err(e) = state.write().unwrap().handle(&TuiIn { exited, event }) { panic!("{e}") } @@ -70,105 +53,3 @@ impl TuiIn { }) } } - -//#[cfg(feature = "dsl")] -//impl DslInput for TuiIn { - //fn matches_dsl (&self, token: &str) -> bool { - //if let Some(event) = TuiKey::new(token).build() { - //&event == self.event() - //} else { - //false - //} - //} -//} - -pub struct TuiKey { - valid: bool, - key: Option, - mods: KeyModifiers, -} - -impl TuiKey { - pub fn new (token: impl AsRef) -> Self { - let token = token.as_ref(); - if token.len() < 2 { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else if token.chars().next() != Some('@') { - Self { valid: false, key: None, mods: KeyModifiers::NONE } - } else { - Self { valid: true, key: None, mods: KeyModifiers::NONE }.next(&token[1..]) - } - } - pub fn build (self) -> Option { - if self.valid && self.key.is_some() { - Some(Event::Key(KeyEvent::new(self.key.unwrap(), self.mods))) - } else { - None - } - } - fn next (mut self, token: &str) -> Self { - let mut tokens = token.split('-').peekable(); - while let Some(token) = tokens.next() { - if tokens.peek().is_some() { - match token { - "ctrl" | "Ctrl" | "c" | "C" => self.mods |= KeyModifiers::CONTROL, - "alt" | "Alt" | "m" | "M" => self.mods |= KeyModifiers::ALT, - "shift" | "Shift" | "s" | "S" => { - self.mods |= KeyModifiers::SHIFT; - // + TODO normalize character case, BackTab, etc. - }, - _ => panic!("unknown modifier {token}"), - } - } else { - self.key = if token.len() == 1 { - Some(KeyCode::Char(token.chars().next().unwrap())) - } else { - Some(Self::named_key(token).unwrap_or_else(||panic!("unknown character {token}"))) - } - } - } - self - } - fn named_key (token: &str) -> Option { - use KeyCode::*; - Some(match token { - "up" => Up, - "down" => Down, - "left" => Left, - "right" => Right, - "esc" | "escape" => Esc, - "enter" | "return" => Enter, - "delete" | "del" => Delete, - "tab" => Tab, - "space" => Char(' '), - "comma" => Char(','), - "period" => Char('.'), - "plus" => Char('+'), - "minus" | "dash" => Char('-'), - "equal" | "equals" => Char('='), - "underscore" => Char('_'), - "backtick" => Char('`'), - "lt" => Char('<'), - "gt" => Char('>'), - "cbopen" | "openbrace" => Char('{'), - "cbclose" | "closebrace" => Char('}'), - "bropen" | "openbracket" => Char('['), - "brclose" | "closebracket" => Char(']'), - "pgup" | "pageup" => PageUp, - "pgdn" | "pagedown" => PageDown, - "f1" => F(1), - "f2" => F(2), - "f3" => F(3), - "f4" => F(4), - "f5" => F(5), - "f6" => F(6), - "f7" => F(7), - "f8" => F(8), - "f9" => F(9), - "f10" => F(10), - "f11" => F(11), - "f12" => F(12), - _ => return None, - }) - } -} diff --git a/tui/src/tui_engine/tui_output.rs b/tui/src/tui_engine/tui_output.rs index 33b46ff..04cbe94 100644 --- a/tui/src/tui_engine/tui_output.rs +++ b/tui/src/tui_engine/tui_output.rs @@ -12,7 +12,7 @@ impl Output for TuiOut { type Area = [Self::Unit;4]; #[inline] fn area (&self) -> [u16;4] { self.area } #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } - #[inline] fn place + ?Sized> (&mut self, area: [u16;4], content: &T) { + #[inline] fn place <'t, T: Render + ?Sized> (&mut self, area: [u16;4], content: &'t T) { let last = self.area(); *self.area_mut() = area; content.render(self); From e839096cf33f72c000d60b73958e1d6a0ec82be5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 14:20:55 +0300 Subject: [PATCH 135/178] docs: operators idea --- dsl/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dsl/README.md b/dsl/README.md index a5bcf22..6220947 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -113,3 +113,13 @@ this is the trait which differentiates "a thing" from * e1: Unexpected '(' * e2: Unexpected 'b' * e3: Unexpected 'd' + +## todo + +### operators + +* replace: `(:= :name :value1 :valueN)` +* append: `(:+ :name :value2 :valueN)` +* filter: `(:- :name :value2 :valueN)` +* map: `(:* :name op)` +* reduce: `(:/ :name op)` From 7fd6c91643cbcfece56ebc14500c6a1ab775fc9e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 17:00:58 +0300 Subject: [PATCH 136/178] need const trie :( --- tui/src/tui_engine/tui_event.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tui/src/tui_engine/tui_event.rs b/tui/src/tui_engine/tui_event.rs index 93dd4ff..f0ab399 100644 --- a/tui/src/tui_engine/tui_event.rs +++ b/tui/src/tui_engine/tui_event.rs @@ -27,7 +27,6 @@ impl TuiKey { let mut modifiers = KeyModifiers::NONE; let mut tokens = symbol[1..].split(Self::SPLIT).peekable(); while let Some(token) = tokens.next() { - println!("{token}"); if tokens.peek().is_some() { match token { "ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL, From 35a5784d23fdcfd15daa28728f18c4924fc945c4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 21:05:05 +0300 Subject: [PATCH 137/178] stack: support above/below --- output/src/ops.rs | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/output/src/ops.rs b/output/src/ops.rs index bdbcff0..a1c3de3 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -776,7 +776,7 @@ pub struct Stack { direction: Direction, callback: F } -impl Stack { +impl)->())->()> Stack { pub fn new (direction: Direction, callback: F) -> Self { Self { direction, callback, __: Default::default(), } } @@ -792,6 +792,12 @@ impl Stack { pub fn west (callback: F) -> Self { Self::new(West, callback) } + pub fn above (callback: F) -> Self { + Self::new(Above, callback) + } + pub fn below (callback: F) -> Self { + Self::new(Below, callback) + } } impl)->())->()> Content for Stack { fn layout (&self, to: E::Area) -> E::Area { @@ -802,31 +808,23 @@ impl)->())->()> Content for St (self.callback)(&mut move |component: &dyn Render|{ let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); match self.direction { - South => { - y = y.plus(h); - h_used = h_used.plus(h); - h_remaining = h_remaining.minus(h); - w_used = w_used.max(w); - }, - East => { - x = x.plus(w); - w_used = w_used.plus(w); - w_remaining = w_remaining.minus(w); - h_used = h_used.max(h); - }, - North | West => { - todo!() - }, + South => { y = y.plus(h); + h_used = h_used.plus(h); + h_remaining = h_remaining.minus(h); + w_used = w_used.max(w); }, + East => { x = x.plus(w); + w_used = w_used.plus(w); + w_remaining = w_remaining.minus(w); + h_used = h_used.max(h); }, + North | West => { todo!() }, + Above | Below => {}, _ => unreachable!(), } }); match self.direction { - North | West => { - todo!() - }, - South | East => { - [to.x(), to.y(), w_used.into(), h_used.into()].into() - }, + North | West => { todo!() }, + South | East => { [to.x(), to.y(), w_used.into(), h_used.into()].into() }, + Above | Below => { [to.x(), to.y(), to.w(), to.h()].into() }, _ => unreachable!(), } } @@ -850,6 +848,9 @@ impl)->())->()> Content for St w_used = w_used.plus(layout.h()); to.place(layout, component); }, + Above | Below => { + to.place(layout, component); + } North | West => { todo!() }, From ab0dc3fae0aaad657a5519414991f5eef47c2514 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 21:50:03 +0300 Subject: [PATCH 138/178] dsl: add ns --- dsl/src/dsl.rs | 2 +- dsl/src/dsl_conv.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index e9d108b..cb2ee5c 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -81,7 +81,7 @@ pub const fn peek_tail (src: &str) -> DslPerhaps<&str> { Ok(None) => Ok(None), Ok(Some((start, length))) => { let tail = str_range(src, start + length, src.len()); - for_each!((i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) }); + for_each!((_i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) }); Ok(None) }, } diff --git a/dsl/src/dsl_conv.rs b/dsl/src/dsl_conv.rs index ae214dd..033e6eb 100644 --- a/dsl/src/dsl_conv.rs +++ b/dsl/src/dsl_conv.rs @@ -1,5 +1,43 @@ use crate::*; +/// Namespace. Currently linearly searched. +pub struct DslNs<'t, T: 't>(pub &'t [(&'t str, T)]); + +/// Namespace where keys are symbols. +pub trait DslSymNs<'t, T: 't>: 't { + const SYMS: DslNs<'t, fn (&'t Self)->T>; + fn from_sym (&'t self, dsl: D) -> Usually { + if let Some(dsl) = dsl.sym()? { + for (sym, get) in Self::SYMS.0 { + if dsl == *sym { + return Ok(get(self)) + } + } + } + return Err(format!("not found: sym: {dsl:?}").into()) + } +} + +#[macro_export] macro_rules! dsl_sym ( + (|$state:ident:$State:ty| -> $type:ty {$($lit:literal => $exp:expr),* $(,)?})=>{ + impl<'t> DslSymNs<'t, $type> for $State { + const SYMS: DslNs<'t, fn (&'t $State)->$type> = + DslNs(&[$(($lit, |$state: &$State|$exp)),*]); } }); + +pub trait DslExpNs<'t, T: 't>: 't { + const EXPS: DslNs<'t, fn (&'t Self, &str)->T>; +} + +#[macro_export] macro_rules! dsl_exp ( + (|$state:ident:$State:ty|->$type:ty { $( + [$key:literal $(/ $sub:ident: $Sub:ty)? $(, $arg:ident $(?)? :$argtype:ty)*] => $body:expr + ),* $(,)? }) => { + impl<'t> DslExpNs<'t, $type> for $State { + const EXPS: DslNs<'t, fn (&'t $State, &str)->$type> = + DslNs(&[]); } }); + +// TODO DEPRECATE: + /// `T` + [Dsl] -> `Self`. pub trait FromDsl: Sized { fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; From ab1afa219f520138ff1a089de4223e52298b1d0e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 21:50:17 +0300 Subject: [PATCH 139/178] input, output: formatting, warnings --- input/src/input_dsl.rs | 12 ++++----- output/src/ops.rs | 59 +++++++++++++----------------------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index cc54282..f4cd98f 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -62,7 +62,7 @@ impl EventMap { /// Create event map from path to text file. pub fn load_from_path <'s> ( &'s mut self, path: impl AsRef - ) -> Usually<&mut Self> where Self: DslInto + DslInto { + ) -> Usually<&'s mut Self> where Self: DslInto + DslInto { if exists(path.as_ref())? { let source = read_to_string(&path)?; let path: Arc = Arc::new(path.as_ref().into()); @@ -81,7 +81,7 @@ impl EventMap { /// Load one event binding into the event map. pub fn load_from_source_one <'s> ( &'s mut self, dsl: impl Dsl, path: &Option<&Arc> - ) -> Usually<&mut Self> where Self: DslInto + DslInto { + ) -> Usually<&'s mut Self> where Self: DslInto + DslInto { if let Some(exp) = dsl.head()?.exp()? && let Some(sym) = exp.head()?.sym()? && let Some(tail) = exp.tail()? @@ -117,10 +117,10 @@ impl EventMap { } impl Binding { pub fn from_dsl (dsl: impl Dsl) -> Usually { - let mut command: Option = None; - let mut condition: Option = None; - let mut description: Option> = None; - let mut source: Option> = None; + let command: Option = None; + let condition: Option = None; + let description: Option> = None; + let source: Option> = None; if let Some(command) = command { Ok(Self { command, condition, description, source }) } else { diff --git a/output/src/ops.rs b/output/src/ops.rs index a1c3de3..f16d971 100644 --- a/output/src/ops.rs +++ b/output/src/ops.rs @@ -777,27 +777,15 @@ pub struct Stack { callback: F } impl)->())->()> Stack { + pub fn north (callback: F) -> Self { Self::new(North, callback) } + pub fn south (callback: F) -> Self { Self::new(South, callback) } + pub fn east (callback: F) -> Self { Self::new(East, callback) } + pub fn west (callback: F) -> Self { Self::new(West, callback) } + pub fn above (callback: F) -> Self { Self::new(Above, callback) } + pub fn below (callback: F) -> Self { Self::new(Below, callback) } pub fn new (direction: Direction, callback: F) -> Self { Self { direction, callback, __: Default::default(), } } - pub fn north (callback: F) -> Self { - Self::new(North, callback) - } - pub fn south (callback: F) -> Self { - Self::new(South, callback) - } - pub fn east (callback: F) -> Self { - Self::new(East, callback) - } - pub fn west (callback: F) -> Self { - Self::new(West, callback) - } - pub fn above (callback: F) -> Self { - Self::new(Above, callback) - } - pub fn below (callback: F) -> Self { - Self::new(Below, callback) - } } impl)->())->()> Content for Stack { fn layout (&self, to: E::Area) -> E::Area { @@ -808,6 +796,8 @@ impl)->())->()> Content for St (self.callback)(&mut move |component: &dyn Render|{ let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); match self.direction { + North | West => { todo!() }, + Above | Below => {}, South => { y = y.plus(h); h_used = h_used.plus(h); h_remaining = h_remaining.minus(h); @@ -816,16 +806,12 @@ impl)->())->()> Content for St w_used = w_used.plus(w); w_remaining = w_remaining.minus(w); h_used = h_used.max(h); }, - North | West => { todo!() }, - Above | Below => {}, - _ => unreachable!(), } }); match self.direction { North | West => { todo!() }, South | East => { [to.x(), to.y(), w_used.into(), h_used.into()].into() }, Above | Below => { [to.x(), to.y(), to.w(), to.h()].into() }, - _ => unreachable!(), } } fn render (&self, to: &mut E) { @@ -836,25 +822,16 @@ impl)->())->()> Content for St (self.callback)(&mut move |component: &dyn Render|{ let layout = component.layout([x, y, w_remaining, h_remaining].into()); match self.direction { - South => { - y = y.plus(layout.h()); - h_remaining = h_remaining.minus(layout.h()); - h_used = h_used.plus(layout.h()); - to.place(layout, component); - }, - East => { - x = x.plus(layout.w()); - w_remaining = w_remaining.minus(layout.w()); - w_used = w_used.plus(layout.h()); - to.place(layout, component); - }, - Above | Below => { - to.place(layout, component); - } - North | West => { - todo!() - }, - _ => unreachable!() + Above | Below => { to.place(layout, component); } + North | West => { todo!() }, + South => { y = y.plus(layout.h()); + h_remaining = h_remaining.minus(layout.h()); + h_used = h_used.plus(layout.h()); + to.place(layout, component); }, + East => { x = x.plus(layout.w()); + w_remaining = w_remaining.minus(layout.w()); + w_used = w_used.plus(layout.h()); + to.place(layout, component); }, } }); } From 3298d6b6e1f7fef2fa9f610b7c1b035a400b4142 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 12 Aug 2025 13:15:00 +0300 Subject: [PATCH 140/178] tui: trim all strings no newline or wrapping yet --- tui/src/tui_content/tui_string.rs | 33 +++++++++++++++++++-------- tui/src/tui_engine/tui_output.rs | 37 ++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/tui/src/tui_content/tui_string.rs b/tui/src/tui_content/tui_string.rs index 92b91fc..57ae7f4 100644 --- a/tui/src/tui_content/tui_string.rs +++ b/tui/src/tui_content/tui_string.rs @@ -3,22 +3,37 @@ use crate::ratatui::prelude::Position; use unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; impl_content_layout_render!(TuiOut: |self: &str, to| - layout = to.center_xy([self.chars().count() as u16, 1]); - render = {let [x, y, ..] = Content::layout(self, to.area()); - to.blit(self, x, y, None)}); + layout = to.center_xy([width_chars_max(to.w(), self), 1]); + render = {let [x, y, w, ..] = Content::layout(self, to.area()); + to.text(self, x, y, w)}); + impl_content_layout_render!(TuiOut: |self: String, to| - layout = to.center_xy([self.chars().count() as u16, 1]); - render = {let [x, y, ..] = Content::layout(self, to.area()); - to.blit(self, x, y, None)}); + layout = Content::::layout(&self.as_str(), to); + render = Content::::render(&self.as_str(), to)); + +impl_content_layout_render!(TuiOut: |self: Arc, to| + layout = Content::::layout(&self.as_ref(), to); + render = Content::::render(&self.as_ref(), to)); + impl_content_layout_render!(TuiOut: |self: std::sync::RwLock, to| layout = Content::::layout(&self.read().unwrap(), to); render = Content::::render(&self.read().unwrap(), to)); + impl_content_layout_render!(TuiOut: |self: std::sync::RwLockReadGuard<'_, String>, to| layout = Content::::layout(&**self, to); render = Content::::render(&**self, to)); -impl_content_layout_render!(TuiOut: |self: Arc, to| - layout = to.center_xy([self.chars().count() as u16, 1]); - render = to.blit(self, to.area.x(), to.area.y(), None)); + +fn width_chars_max (max: u16, text: impl AsRef) -> u16 { + let mut width: u16 = 0; + let mut chars = text.as_ref().chars(); + while let Some(c) = chars.next() { + width += c.width().unwrap_or(0) as u16; + if width > max { + break + } + } + return width +} /// Trim string with [unicode_width]. pub fn trim_string (max_width: usize, input: impl AsRef) -> String { diff --git a/tui/src/tui_engine/tui_output.rs b/tui/src/tui_engine/tui_output.rs index 04cbe94..9b35936 100644 --- a/tui/src/tui_engine/tui_output.rs +++ b/tui/src/tui_engine/tui_output.rs @@ -1,6 +1,7 @@ use crate::*; use std::time::Duration; use std::thread::{spawn, JoinHandle}; +use unicode_width::*; #[derive(Default)] pub struct TuiOut { pub buffer: Buffer, @@ -10,8 +11,12 @@ impl Output for TuiOut { type Unit = u16; type Size = [Self::Unit;2]; type Area = [Self::Unit;4]; - #[inline] fn area (&self) -> [u16;4] { self.area } - #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } + #[inline] fn area (&self) -> [u16;4] { + self.area + } + #[inline] fn area_mut (&mut self) -> &mut [u16;4] { + &mut self.area + } #[inline] fn place <'t, T: Render + ?Sized> (&mut self, area: [u16;4], content: &'t T) { let last = self.area(); *self.area_mut() = area; @@ -63,10 +68,32 @@ impl TuiOut { pub fn blit ( &mut self, text: &impl AsRef, x: u16, y: u16, style: Option