diff --git a/Cargo.lock b/Cargo.lock index 6d96025..e5500fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,16 +285,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dizzle" -version = "0.1.0" -dependencies = [ - "const_panic", - "itertools 0.14.0", - "konst", - "thiserror", -] - [[package]] name = "document-features" version = "0.2.11" @@ -1055,19 +1045,38 @@ name = "tengri" version = "0.14.0" dependencies = [ "crossterm 0.29.0", - "dizzle", "tengri", + "tengri_core", + "tengri_dsl", "tengri_input", "tengri_output", "tengri_proc", "tengri_tui", ] +[[package]] +name = "tengri_core" +version = "0.14.0" + +[[package]] +name = "tengri_dsl" +version = "0.14.0" +dependencies = [ + "const_panic", + "itertools 0.14.0", + "konst", + "proptest", + "tengri_core", + "tengri_tui", + "thiserror", +] + [[package]] name = "tengri_input" version = "0.14.0" dependencies = [ - "dizzle", + "tengri_core", + "tengri_dsl", "tengri_tui", ] @@ -1076,10 +1085,11 @@ name = "tengri_output" version = "0.14.0" dependencies = [ "bumpalo", - "dizzle", "proptest", "proptest-derive", "tengri", + "tengri_core", + "tengri_dsl", "tengri_tui", ] @@ -1087,11 +1097,11 @@ dependencies = [ name = "tengri_proc" version = "0.14.0" dependencies = [ - "dizzle", "heck", "proc-macro2", "quote", "syn", + "tengri_core", ] [[package]] @@ -1102,12 +1112,14 @@ dependencies = [ "better-panic", "bumpalo", "crossterm 0.29.0", - "dizzle", + "konst", "palette", "quanta", "rand 0.8.5", "ratatui", "tengri", + "tengri_core", + "tengri_dsl", "tengri_input", "tengri_output", "tengri_proc", diff --git a/Cargo.toml b/Cargo.toml index 7619d5c..f2280f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,11 @@ lto = false resolver = "2" members = [ "./tengri", + "./core", "./input", "./output", "./tui", + "./dsl", "./proc", ] @@ -20,20 +22,23 @@ version = "0.14.0" edition = "2024" [workspace.dependencies] -dizzle = { path = "../dizzle" } - tengri = { path = "./tengri" } +tengri_core = { path = "./core" } tengri_input = { path = "./input" } tengri_output = { path = "./output" } tengri_tui = { path = "./tui" } +tengri_dsl = { path = "./dsl" } tengri_proc = { path = "./proc" } anyhow = { version = "1.0" } atomic_float = { version = "1" } better-panic = { version = "0.3.0" } bumpalo = { version = "3.19.0" } +const_panic = { version = "0.2.12", features = [ "derive" ] } crossterm = { version = "0.29.0" } 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" } @@ -43,4 +48,5 @@ 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/core_macros.rs b/core/src/core_macros.rs new file mode 100644 index 0000000..ef56421 --- /dev/null +++ b/core/src/core_macros.rs @@ -0,0 +1,137 @@ +/// 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 and implement it for read-only wrapper types. +#[macro_export] macro_rules! flex_trait ( + ($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? { + $(fn $fn:ident $(<$($fl:lifetime),*>)? (& $($fl2:lifetime)* $self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)* + }) => { + pub trait $Trait $(<$($A: $T),+>)? $(:$dep $(<$dtt>)? $(+$dep2 $(<$dtt2>)?)*)? { + $(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? $body)* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &_T_ { + $(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for &mut _T_ { + $(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (**$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)? _T_: $Trait $(<$($A),+>)?> $Trait $(<$($A),+>)? for ::std::sync::Arc<_T_> { + $(fn $fn $(<$($fl),*>)? (& $($fl2)* $self $(,$arg:$ty)*) $(-> $ret)? { (*$self).$fn($($arg),*) })* + } + impl<$($($A: $T,)+)?> $Trait $(<$($A),+>)? for Box)?> { + $(fn $fn $(<$($fl),*>)? (& $($fl2)* $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 read-only wrapper types. */ +#[macro_export] macro_rules! flex_trait_sized ( + ($Trait:ident $(<$($A:ident:$T:ident),+>)? $(:$dep:ident $(<$dtt:tt>)? $(+$dep2:ident $(<$dtt2:tt>)?)*)? { + $(fn $fn:ident (&$self:ident $(, $arg:ident:$ty:ty)*) $(-> $ret:ty)? $body:block)* + }) => { + pub trait $Trait $(<$($A: $T),+>)? : $($dep $(<$dtt>+)? $($dep2 $(<$dtt2>)?)*+)? Sized { + $(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,)+)?> $Trait $(<$($A),+>)? for Box)?> { + $(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)* + })=>{ + 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 new file mode 100644 index 0000000..7bf320e --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,45 @@ +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>; + +/// Type-dispatched `get` and `get_mut`. +pub trait Has: Send + Sync { fn get (&self) -> &T; fn get_mut (&mut self) -> &mut T; } + +/// 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>; } + +/// 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()) + } + } +} + +#[macro_export] macro_rules! as_ref { + ($T:ty: |$self:ident : $S:ty| $x:expr) => { + impl AsRef<$T> for $S { + fn as_ref (&$self) -> &$T { &$x } + } + }; +} + +pub fn wrap_inc (index: usize, count: usize) -> usize { + if count > 0 { (index + 1) % count } else { 0 } +} + +pub fn wrap_dec (index: usize, count: usize) -> usize { + if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 } +} diff --git a/dsl/Cargo.lock b/dsl/Cargo.lock new file mode 100644 index 0000000..26585b0 --- /dev/null +++ b/dsl/Cargo.lock @@ -0,0 +1,980 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clojure-reader" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edf141eea627c101a97509266bc9f6ba8cd408618f5e2ac4a0cb6b64b1d4ea8" +dependencies = [ + "ordered-float", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "const_panic" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "894813a444908c0c8c0e221b041771d107c4a21de1d317dc49bcc66e3c9e5b3f" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "konst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +dependencies = [ + "const_panic", + "konst_kernel", + "konst_proc_macros", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" +dependencies = [ + "typewit", +] + +[[package]] +name = "konst_proc_macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00af7901ba50898c9e545c24d5c580c96a982298134e8037d8978b6594782c07" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", + "rand", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tengri_dsl" +version = "0.1.0" +dependencies = [ + "clojure-reader", + "itertools 0.14.0", + "konst", + "tengri_tui", +] + +[[package]] +name = "tengri_input" +version = "0.2.0" + +[[package]] +name = "tengri_output" +version = "0.2.0" +dependencies = [ + "tengri_dsl", +] + +[[package]] +name = "tengri_tui" +version = "0.2.0" +dependencies = [ + "better-panic", + "crossterm", + "palette", + "rand", + "ratatui", + "tengri_dsl", + "tengri_input", + "tengri_output", +] + +[[package]] +name = "typewit" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +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", +] + +[[package]] +name = "windows-targets" +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", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml new file mode 100644 index 0000000..c61a057 --- /dev/null +++ b/dsl/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tengri_dsl" +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 } +const_panic = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +tengri_tui = { path = "../tui" } +proptest = "^1.6.0" diff --git a/dsl/README.md b/dsl/README.md new file mode 100644 index 0000000..0ef496d --- /dev/null +++ b/dsl/README.md @@ -0,0 +1,68 @@ +[***dizzle***](https://codeberg.org/unspeaker/tengri/src/branch/main/dsl) +is a means of adding a tiny interpreted domain-specific language to your programs. + +dizzle currently provides an s-expression based syntax. + +dizzle parses source code by means of the `Dsl`, `DslExpr` and `DslWord` traits. +those are implemented for basic stringy types and their `Option` and `Result` wrapped analogs. +to customize parsing, define and use your own traits on top of the provided ones. + +dizzle evaluates the parsed source code by means of the `DslNs` trait. the methods of +this trait match literals, words, and expressions, against pre-defined lists. the +`dsl_words` and `dsl_exprs` macros let you define those lists slightly less verbosely. + +## goals + +* [x] const parse +* [ ] live reload +* [ ] serialize modified code back to original indentation + +## examples + +### in [`tengri_output`](../output) + +```edn +(bsp/s (fixed/y 2 :toolbar) + (fill/x (align/c (bsp/w :pool + (bsp/s :outputs (bsp/s :inputs (bsp/s :tracks :scenes))))))) +``` + +### in [`tengri_input`](../input) + +```edn +(@u undo 1) +(@shift-u redo 1) +(@e editor show :pool-clip) +(@ctrl-a scene add) +(@ctrl-t track add) +(@tab pool toggle) +``` + +## implementation notes + +### `DslExpr` 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' + +### possible design for operator-based syntax + +* replace: `(:= :name :value1 :valueN)` +* append: `(:+ :name :value2 :valueN)` +* filter: `(:- :name :value2 :valueN)` +* map: `(:* :name op)` +* reduce: `(:/ :name op)` diff --git a/dsl/proptest-regressions/iter.txt b/dsl/proptest-regressions/iter.txt new file mode 100644 index 0000000..b4d8405 --- /dev/null +++ b/dsl/proptest-regressions/iter.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc bbb90b16e6106f17dbb5a4f57594f451360a2ea7e3e20c28adeb8babc98d39df # shrinks to source = "(𰀀" diff --git a/dsl/proptest-regressions/token.txt b/dsl/proptest-regressions/token.txt new file mode 100644 index 0000000..cc03cf2 --- /dev/null +++ b/dsl/proptest-regressions/token.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 5fb814b74ae035bdecb536817090cfb473f0a874e9acf9aaa136a4794cdb367f # shrinks to source = "", start = 10336420442936153584, length = 8110323630773398032 diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs new file mode 100644 index 0000000..ffdf38c --- /dev/null +++ b/dsl/src/dsl.rs @@ -0,0 +1,227 @@ +//#![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)] +#![feature(type_alias_impl_trait)] +extern crate const_panic; +use const_panic::PanicFmt; +use std::fmt::Debug; +pub(crate) use ::{ + std::sync::Arc, + //std::error::Error, + konst::iter::for_each, + konst::string::{str_from, str_range, char_indices}, + thiserror::Error, + tengri_core::* +}; +pub(crate) use self::DslError::*; +mod dsl_error; pub use self::dsl_error::*; +mod dsl_ns; pub use self::dsl_ns::*; +mod dsl_word; pub use self::dsl_word::*; +mod dsl_expr; pub use self::dsl_expr::*; +mod dsl_text; pub use self::dsl_text::*; +#[cfg(test)] mod dsl_test; + +// Trait that designates any string-like as potentially parsable DSL. +pub trait Dsl: Debug + Send + Sync { + fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } +} +impl<'x> Dsl for &'x str { + fn src (&self) -> DslPerhaps<&str> { Ok(Some(self)) } +} +impl Dsl for Arc { + fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } +} +impl Dsl for &D { + fn src (&self) -> DslPerhaps<&str> { (*self).src() } +} +impl Dsl for &mut D { + fn src (&self) -> DslPerhaps<&str> { (**self).src() } +} +impl Dsl for Option { + fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} +} +impl Dsl for Result { + fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}} +} + +/// DSL-specific result type. +pub type DslResult = Result; + +/// DSL-specific optional result type. +pub type DslPerhaps = Result, DslError>; + +pub const fn peek (src: &str) -> DslPerhaps<&str> { + Ok(Some(if let Ok(Some(expr)) = expr_peek(src) { expr } else + if let Ok(Some(word)) = word_peek(src) { word } 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(expr)) = expr_seek(src) { expr } else + if let Ok(Some(word)) = word_seek(src) { word } 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) + }, + } +} + +pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> { + match expr_peek(src) { + Ok(Some(peeked)) => { + let len = peeked.len(); + let start = if len > 0 { 1 } else { 0 }; + Ok(Some(str_range(src, start, start + len.saturating_sub(2)))) + }, + e => e + } +} + +pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> { + match expr_seek(src) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => { + if let Err(e) = no_trailing_non_space(src, start + length, Some("expr_peek_inner_only")) { + Err(e) + } else { + let peeked = str_range(src, 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_space (c: char) -> bool { + matches!(c, ' '|'\n'|'\r'|'\t') +} + +pub const fn no_trailing_non_space ( + src: &str, offset: usize, context: Option<&'static str> +) -> DslResult<()> { + Ok(for_each!((i, c) in char_indices(str_range(src, offset, src.len())) => if !is_space(c) { + return Err(Unexpected(c, Some(offset + i), if let Some(context) = context { + Some(context) + } else { + Some("trailing non-space") + })) + })) +} + +pub const fn is_word_char (c: char) -> bool { + matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/'|'@'|':') +} + +pub const fn is_word_end (c: char) -> bool { + is_space(c) || is_expr_end(c) +} + +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 is_expr_start (c: char) -> bool { + c == '(' +} + +pub const fn is_expr_end (c: char) -> bool { + c == ')' +} + +pub const fn is_digit (c: char) -> bool { + matches!(c, '0'..='9') +} + +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, None, Some("parse digit"))) + }) +} + +pub(crate) fn ok_flat (x: Option>) -> DslPerhaps { + Ok(x.transpose()?.flatten()) +} + +#[macro_export] macro_rules! dsl_type (($T:ident { $($trait:tt)* } { + pub const fn $peek:ident $($_1:tt)?; + pub const fn $peek_only:ident $($_2:tt)?; + pub const fn $seek:ident $($_3:tt)?; + pub const fn $seek_start:ident ($source1:ident) $body1:block + pub const fn $seek_length:ident ($source2:ident) $body2:block +})=>{ + pub trait $T: Dsl { $($trait)* } + impl $T for D {} + pub const fn $seek_start ($source1: &str) -> DslPerhaps $body1 + pub const fn $seek_length ($source2: &str) -> DslPerhaps $body2 + /// 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), + }, + } + } + /// 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_space(source, start + length, Some("peek_only")) { + Err(e) + } else { + Ok(Some(str_range(source, start, start + length))) + } + } + } + } +}); diff --git a/dsl/src/dsl_error.rs b/dsl/src/dsl_error.rs new file mode 100644 index 0000000..11e00c0 --- /dev/null +++ b/dsl/src/dsl_error.rs @@ -0,0 +1,25 @@ +use crate::*; + +/// 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, Option, Option<&'static str>), + + #[error("parse failed: error #{0}")] + Code(u8), + + #[error("end reached")] + End + +} diff --git a/dsl/src/dsl_expr.rs b/dsl/src/dsl_expr.rs new file mode 100644 index 0000000..92b093e --- /dev/null +++ b/dsl/src/dsl_expr.rs @@ -0,0 +1,87 @@ +use crate::*; + +pub type GetDslExpr<'a, S, T> = for<'b> fn(&'a S, &'b str)->Perhaps; + +pub type DslExprs<'a, S, T> = &'a [(&'a str, GetDslExpr<'a, S, T>)]; + +dsl_type!(DslExpr { + fn expr (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(expr_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()? { + cb(head)?; + if let Some(tail) = self.tail()? { + tail.each(cb)?; + } + }) + } +} { + pub const fn expr_peek [generated]; + pub const fn expr_peek_only [generated]; + pub const fn expr_seek [generated]; + pub const fn expr_seek_start (src) { + for_each!((i, c) in char_indices(src) => + if is_expr_start(c) { return Ok(Some(i)) } else + if !is_space(c) { return Err(Unexpected(c, Some(i), Some("expected expression start"))) }); + Ok(None) + } + pub const fn expr_seek_length (src) { + let mut depth = 0; + for_each!((i, c) in char_indices(src) => + if is_expr_start(c) { depth += 1; } else + if is_expr_end(c) { + if depth == 0 { + return Err(Unexpected(c, Some(i), Some("expected expression end"))) + } else if depth == 1 { + return Ok(Some(i + 1)) + } else { + depth -= 1; + } + }); + Err(Incomplete) + } +}); + +#[macro_export] macro_rules!dsl_exprs(($l:lifetime |$state:ident|->$Type:ty$({ + $($name:literal ($($arg:ident:$ty:ty),* $(,)?) => $body:expr),* $(,)? +})?)=>{ + const EXPRS: DslExprs<$l, Self, $Type> = &[$( $({ + let get: GetDslExpr<$l, Self, $Type> = |$state: &$l Self, tail_base|{ + let tail = tail_base; + $( + let head = tail.head()?.unwrap_or_default(); + let tail = tail.tail()?.unwrap_or_default(); + let $arg: $ty = if let Some(arg) = $state.from(&head)? { + arg + } else { + return Err(format!("{}: arg \"{}\" ({}) got: {head} {tail}", + $name, + stringify!($arg), + stringify!($ty), + ).into()) + }; + )* + Ok(Some($body/* as $Type*/)) + }; + ($name, get) + }),* )? ]; +}); + +pub trait DslNsExprs<'a, T: 'a>: 'a { + /// Known expressions. + const EXPRS: DslExprs<'a, Self, T> = &[]; + /// Resolve an expression if known. + fn from_expr (&'a self, dsl: impl DslExpr + 'a) -> Perhaps { + if let Some(dsl) = dsl.expr()? { + let head = dsl.head()?; + for (key, get) in Self::EXPRS.iter() { + if Some(*key) == head { + return get(self, dsl.tail()?.unwrap_or("")) + } + } + } + Ok(None) + } +} diff --git a/dsl/src/dsl_ns.rs b/dsl/src/dsl_ns.rs new file mode 100644 index 0000000..c8b701f --- /dev/null +++ b/dsl/src/dsl_ns.rs @@ -0,0 +1,46 @@ +use crate::*; + +pub trait DslNs<'a, T: 'a>: DslNsWords<'a, T> + DslNsExprs<'a, T> { + /// Resolve an expression or symbol. + fn from (&'a self, dsl: impl Dsl + 'a) -> Perhaps { + if let Ok(Some(literal)) = self.from_literal(&dsl) { + Ok(Some(literal)) + } else if let Ok(Some(meaning)) = self.from_word(&dsl) { + Ok(Some(meaning)) + } else { + self.from_expr(dsl) + } + } + /// Resolve as literal if valid. + fn from_literal (&self, _: impl Dsl) -> Perhaps { + Ok(None) + } +} + +#[macro_export] macro_rules! dsl_ns { + ($State:ty: $Type:ty { + $(literal = |$dsl:ident| $literal:expr;)? + $(word = |$state_w:ident| { + $($word:literal => $body_w:expr),* $(,)? + };)? + $(expr = |$state_e:ident| { + $($head:literal $args:tt => $body_e:expr),* $(,)? + };)? + }) => { + impl<'a> DslNs<'a, $Type> for $State { + $(fn from_literal (&self, $dsl: impl Dsl) -> Perhaps<$Type> { + $literal + })? + } + impl<'a> DslNsWords<'a, $Type> for $State { + $(dsl_words! { 'a |$state_w| -> $Type { + $($word => $body_w),* + } })? + } + impl<'a> DslNsExprs<'a, $Type> for $State { + $(dsl_exprs! { 'a |$state_e| -> $Type { + $($head $args => $body_e),* + } })? + } + } +} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs new file mode 100644 index 0000000..fb6d6f7 --- /dev/null +++ b/dsl/src/dsl_test.rs @@ -0,0 +1,99 @@ +use crate::*; +macro_rules!is_some(($expr:expr, $val:expr)=>{assert_eq!($expr, Ok(Some($val)))};); +macro_rules!is_none(($expr:expr)=>{assert_eq!($expr, Ok(None))};); +macro_rules!is_err(($expr:expr)=>{assert!($expr.is_err())}; + ($expr:expr, $err:expr)=>{assert_eq!($expr, Err($err))};); +#[test] fn test_expr () -> Result<(), DslError> { + let e0 = DslError::Unexpected('a', None, None); + let e1 = DslError::Unexpected('(', None, None); + let e2 = DslError::Unexpected('b', None, None); + let e3 = DslError::Unexpected('d', None, None); + let check = |src: &str, word, expr, head, tail|{ + assert_eq!(src.word(), word, "{src}"); + assert_eq!(src.expr(), expr, "{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"))); + + is_some!(word_peek("\n :view/transport"), ":view/transport"); + + assert!(is_space(' ')); + assert!(!is_word_char(' ')); + assert!(is_word_char('f')); + + is_some!(word_seek_start("foo"), 0); + is_some!(word_seek_start("foo "), 0); + is_some!(word_seek_start(" foo "), 1); + is_some!(word_seek_length(&" foo "[1..]), 3); + is_some!(word_seek("foo"), (0, 3)); + is_some!(word_peek("foo"), "foo"); + is_some!(word_seek("foo "), (0, 3)); + is_some!(word_peek("foo "), "foo"); + is_some!(word_seek(" foo "), (1, 3)); + is_some!(word_peek(" foo "), "foo"); + + is_err!("(foo)".word()); + is_err!("foo".expr()); + + is_some!("(foo)".expr(), "foo"); + is_some!("(foo)".head(), "(foo)"); + is_none!("(foo)".tail()); + + is_some!("(foo bar baz)".expr(), "foo bar baz"); + is_some!("(foo bar baz)".head(), "(foo bar baz)"); + is_none!("(foo bar baz)".tail()); + + is_some!("(foo bar baz)".expr().head(), "foo"); + is_some!("(foo bar baz)".expr().tail(), "bar baz"); + is_some!("(foo bar baz)".expr().tail().head(), "bar"); + is_some!("(foo bar baz)".expr().tail().tail(), "baz"); + + is_err!("foo".expr()); + is_some!("foo".word(), "foo"); + is_some!(" foo".word(), "foo"); + is_some!(" foo ".word(), "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().expr(), "bar"); + is_some!(" (foo) (bar) ".tail().head().expr().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) ".expr(), "foo bar baz"); + is_some!(" (foo bar baz) ".expr().head(), "foo"); + is_some!(" (foo bar baz) ".expr().tail(), "bar baz"); + is_some!(" (foo bar baz) ".expr().tail().head(), "bar"); + is_some!(" (foo bar baz) ".expr().tail().tail(), "baz"); + is_none!(" (foo bar baz) ".tail()); + Ok(()) +} diff --git a/dsl/src/dsl_text.rs b/dsl/src/dsl_text.rs new file mode 100644 index 0000000..9c2e51d --- /dev/null +++ b/dsl/src/dsl_text.rs @@ -0,0 +1,20 @@ +use crate::*; + +dsl_type!(DslText { + fn text (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(text_peek_only)) } +} { + pub const fn text_peek [generated]; + pub const fn text_peek_only [generated]; + pub const fn text_seek [generated]; + pub const fn text_seek_start (src) { + for_each!((i, c) in char_indices(src) => + if is_text_start(c) { return Ok(Some(i)) } else + if !is_space(c) { return Err(Unexpected(c, Some(i), None)) }); + Ok(None) + } + pub const fn text_seek_length (src) { + for_each!((i, c) in char_indices(src) => + if is_text_end(c) { return Ok(Some(i)) }); + Ok(None) + } +}); diff --git a/dsl/src/dsl_word.rs b/dsl/src/dsl_word.rs new file mode 100644 index 0000000..5abb61a --- /dev/null +++ b/dsl/src/dsl_word.rs @@ -0,0 +1,49 @@ +use crate::*; + +pub type GetDslWord<'a, S, T> = fn(&'a S)->Perhaps; + +pub type DslWords<'a, S, T> = &'a [(&'a str, GetDslWord<'a, S, T>)]; + +dsl_type!(DslWord { + fn word (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(word_peek_only))} +} { + pub const fn word_peek [generated]; + pub const fn word_peek_only [generated]; + pub const fn word_seek [generated]; + pub const fn word_seek_start (src) { + for_each!((i, c) in char_indices(src) => if + is_word_char(c) { return Ok(Some(i)) } else + if !is_space(c) { return Err(Unexpected(c, Some(i), Some("word_seek_start"))) }); + Ok(None) + } + pub const fn word_seek_length (src) { + for_each!((i, c) in char_indices(src) => if !is_word_char(c) { return Ok(Some(i)) }); + Ok(Some(src.len())) + } +}); + +#[macro_export] macro_rules!dsl_words(($l:lifetime |$state:ident|->$Type:ty$({ + $($word:literal => $body:expr),* $(,)? +})?)=>{ + const WORDS: DslWords<$l, Self, $Type> = &[$( $({ + let get: GetDslWord<$l, Self, $Type> = |$state: &$l Self|Ok(Some($body)); + ($word, get) + }),* )?]; +}); + +pub trait DslNsWords<'a, T: 'a>: 'a { + /// Known symbols. + const WORDS: DslWords<'a, Self, T> = &[]; + /// Resolve a symbol if known. + fn from_word (&'a self, dsl: impl DslWord) -> Perhaps { + if let Some(dsl) = dsl.word()? { + for (key, get) in Self::WORDS.iter() { + if dsl == *key { + let value = get(self); + return value + } + } + } + return Ok(None) + } +} diff --git a/dsl/test.edn b/dsl/test.edn new file mode 100644 index 0000000..7e79d1a --- /dev/null +++ b/dsl/test.edn @@ -0,0 +1 @@ +(bsp/n (fixed/y 2 :transport) (bsp/s (fixed/y 2 :status) (fill/xy (bsp/a (fill/xy (align/e :pool)) :arranger)))) 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/editor/src/main.rs b/editor/src/main.rs new file mode 100644 index 0000000..e69de29 diff --git a/input/Cargo.toml b/input/Cargo.toml index 506c636..0c9218d 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -8,7 +8,8 @@ edition = { workspace = true } path = "input.rs" [dependencies] -dizzle = { path = "../../dizzle" } +tengri_core = { path = "../core" } [dev-dependencies] tengri_tui = { path = "../tui" } +tengri_dsl = { path = "../dsl" } diff --git a/input/input.rs b/input/input.rs index b251232..5a22800 100644 --- a/input/input.rs +++ b/input/input.rs @@ -1,7 +1,7 @@ #![feature(associated_type_defaults)] #![feature(if_let_guard)] -pub(crate) use dizzle::*; +pub(crate) use tengri_core::*; #[cfg(test)] mod input_test; diff --git a/output/Cargo.toml b/output/Cargo.toml index ac61804..ed7f210 100644 --- a/output/Cargo.toml +++ b/output/Cargo.toml @@ -4,16 +4,21 @@ description = "UI metaframework, output layer." version = { workspace = true } edition = { workspace = true } +[lib] +path = "src/output.rs" + [features] bumpalo = [ "dep:bumpalo" ] -dsl = [] +dsl = [ "dep:tengri_dsl" ] [dependencies] -dizzle = { path = "../../dizzle" } -bumpalo = { optional = true, workspace = true } +tengri_core = { path = "../core" } +tengri_dsl = { optional = true, path = "../dsl" } +bumpalo = { optional = true, workspace = true } [dev-dependencies] tengri = { path = "../tengri", features = [ "dsl", "tui" ] } tengri_tui = { path = "../tui" } +tengri_dsl = { path = "../dsl" } proptest = { workspace = true } proptest-derive = { workspace = true } diff --git a/output/src/layout/layout_bsp.rs b/output/src/layout/layout_bsp.rs index bd65e96..3de4cfc 100644 --- a/output/src/layout/layout_bsp.rs +++ b/output/src/layout/layout_bsp.rs @@ -22,7 +22,7 @@ impl, Tail: Content> Draw for Bsp { fn draw (&self, to: &mut O) { match self.0 { South => { - //panic!("{}", self.1.h(to.area())); + panic!("{}", self.1.h(to.area())); let area_1 = self.1.layout(to.area()); let area_2 = self.2.layout([ to.area().x(), @@ -30,7 +30,7 @@ impl, Tail: Content> Draw for Bsp { to.area().w(), to.area().h().minus(area_1.h()) ].into()); - //panic!("{area_1:?} {area_2:?}"); + panic!("{area_1:?} {area_2:?}"); to.place_at(area_1, &self.1); to.place_at(area_2, &self.2); }, diff --git a/output/src/lib.rs b/output/src/lib.rs deleted file mode 100644 index 17e62f7..0000000 --- a/output/src/lib.rs +++ /dev/null @@ -1,200 +0,0 @@ -#![feature(step_trait)] -#![feature(type_alias_impl_trait)] -#![feature(impl_trait_in_assoc_type)] -#![feature(const_precise_live_drops)] -#![feature(type_changing_struct_update)] -#![feature(anonymous_lifetime_in_impl_trait)] -#![feature(const_option_ops)] -#![feature(const_trait_impl)] -#![feature(const_default)] -#![feature(trait_alias)] -//#![feature(non_lifetime_binders)] -pub(crate) use self::Direction::*; -pub(crate) use std::fmt::{Debug, Display}; -pub(crate) use std::marker::PhantomData; -pub(crate) use std::ops::{Add, Sub, Mul, Div}; -pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}}; -pub(crate) use dizzle::*; -mod output; pub use self::output::*; -mod content; pub use self::content::*; -mod draw; pub use self::draw::*; -mod group; pub use self::group::*; -mod layout; pub use self::layout::*; -mod space; pub use self::space::*; -mod thunk; pub use self::thunk::*; -mod widget; pub use self::widget::*; -#[cfg(feature = "dsl")] mod view; -#[cfg(feature = "dsl")] pub use self::view::*; -#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; -#[cfg(test)] mod test { - use crate::{*, Direction::*}; - //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)), - (None, Some(y)) => Some($Op::y(y, content)), - _ => None - } { - //assert_eq!(Content::layout(&op, [x, y, w, h]), - //Draw::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_padding, Pad); - - 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]), - //Draw::layout(&bsp, [x, y, w, h]), - //); - } - } - - #[test] fn test_stub_output () -> Usually<()> { - use crate::*; - struct TestOut([u16;4]); - impl Out for TestOut { - 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_at + ?Sized> (&mut self, area: [u16;4], _: &T) { - println!("place_at: {area:?}"); - () - } - } - impl Draw for String { - fn draw (&self, to: &mut TestOut) { - 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 Draw { - Map::new(||data.iter(), |_foo, _index|{}) - } - let _data = vec![Foo, Foo, Foo]; - //let map = make_map(&data); - } -} diff --git a/output/src/output.rs b/output/src/output.rs index 7f0294e..5da748f 100644 --- a/output/src/output.rs +++ b/output/src/output.rs @@ -1,4 +1,21 @@ -use crate::*; +#![feature(step_trait)] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] +#![feature(const_precise_live_drops)] +#![feature(type_changing_struct_update)] +#![feature(anonymous_lifetime_in_impl_trait)] +#![feature(const_option_ops)] +#![feature(const_trait_impl)] +#![feature(const_default)] +#![feature(trait_alias)] +//#![feature(non_lifetime_binders)] + +pub(crate) use self::Direction::*; +pub(crate) use std::fmt::{Debug, Display}; +pub(crate) use std::marker::PhantomData; +pub(crate) use std::ops::{Add, Sub, Mul, Div}; +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}}; +pub(crate) use tengri_core::*; /// Drawing target. pub trait Out: Send + Sync + Sized { @@ -32,3 +49,17 @@ pub trait Out: Send + Sync + Sized { /// Mutable pointer to area. fn area_mut (&mut self) -> &mut Self::Area; } + +#[cfg(test)] mod output_test; +#[cfg(test)] pub(crate) use proptest_derive::Arbitrary; + +mod content; pub use self::content::*; +mod draw; pub use self::draw::*; +mod group; pub use self::group::*; +mod layout; pub use self::layout::*; +mod space; pub use self::space::*; +mod thunk; pub use self::thunk::*; +mod widget; pub use self::widget::*; + +#[cfg(feature = "dsl")] mod view; +#[cfg(feature = "dsl")] pub use self::view::*; diff --git a/output/src/output_test.rs b/output/src/output_test.rs new file mode 100644 index 0000000..932e87a --- /dev/null +++ b/output/src/output_test.rs @@ -0,0 +1,170 @@ +use crate::{*, Direction::*}; +//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)), + (None, Some(y)) => Some($Op::y(y, content)), + _ => None + } { + //assert_eq!(Content::layout(&op, [x, y, w, h]), + //Draw::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_padding, Pad); + +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]), + //Draw::layout(&bsp, [x, y, w, h]), + //); + } +} + +#[test] fn test_stub_output () -> Usually<()> { + use crate::*; + struct TestOut([u16;4]); + impl Out for TestOut { + 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_at + ?Sized> (&mut self, area: [u16;4], _: &T) { + println!("place_at: {area:?}"); + () + } + } + impl Draw for String { + fn draw (&self, to: &mut TestOut) { + 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 Draw { + 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 index 8e62c91..f8a182f 100644 --- a/output/src/view.rs +++ b/output/src/view.rs @@ -1,5 +1,5 @@ use crate::*; -use ::dizzle::{Dsl, DslExpr, DslWord, DslNs}; +use ::tengri_dsl::{Dsl, DslExpr, DslWord, DslNs}; pub trait View { fn view_expr <'a> (&'a self, output: &mut O, expr: &'a impl DslExpr) -> Usually { diff --git a/proc/Cargo.toml b/proc/Cargo.toml index 839b1c6..19fe50c 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -8,7 +8,7 @@ edition = { workspace = true } proc-macro = true [dependencies] -dizzle = { path = "../../dizzle" } +tengri_core = { path = "../core" } syn = { workspace = true } quote = { workspace = true } proc-macro2 = { workspace = true } diff --git a/tengri/Cargo.toml b/tengri/Cargo.toml index e67c76e..74b46ad 100644 --- a/tengri/Cargo.toml +++ b/tengri/Cargo.toml @@ -9,17 +9,18 @@ default = [ "input", "output", "tui" ] input = [ "tengri_input" ] output = [ "tengri_output" ] tui = [ "tengri_tui" ] -dsl = [ "tengri_output/dsl", "tengri_tui/dsl" ] +dsl = [ "tengri_dsl", "tengri_output/dsl", "tengri_tui/dsl" ] [dependencies] -dizzle = { workspace = true } -tengri_input = { workspace = true, optional = true } -tengri_output = { workspace = true, optional = true } -tengri_tui = { workspace = true, optional = true } +tengri_core = { workspace = true } +tengri_dsl = { optional = true, path = "../dsl" } +tengri_input = { optional = true, path = "../input" } +tengri_output = { optional = true, path = "../output" } +tengri_tui = { optional = true, path = "../tui" } [dev-dependencies] -tengri_proc = { workspace = true } -tengri = { workspace = true, features = [ "dsl" ] } +tengri_proc = { path = "../proc" } +tengri = { path = ".", features = [ "dsl" ] } crossterm = { workspace = true } [target.'cfg(target_os = "linux")'] diff --git a/tengri/src/lib.rs b/tengri/src/lib.rs index 5b12960..972a702 100644 --- a/tengri/src/lib.rs +++ b/tengri/src/lib.rs @@ -1,112 +1,8 @@ -pub use ::dizzle::*; +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; #[cfg(feature="tui")] pub use ::tengri_tui as tui; #[cfg(test)] extern crate tengri_proc; -#[cfg(test)] mod test { - // 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 } - - //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 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) - //")? - //}; - - ////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..2df3c8a --- /dev/null +++ b/tengri/src/test.rs @@ -0,0 +1,103 @@ +// 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 } + + //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 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) + //")? + //}; + + ////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/Cargo.toml b/tui/Cargo.toml index 236e778..3e9ab61 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -4,20 +4,24 @@ description = "UI metaframework, Ratatui backend." version = { workspace = true } edition = { workspace = true } +[lib] +path = "src/tui.rs" + [features] -dsl = [ "tengri_output/dsl" ] +dsl = [ "dep:tengri_dsl", "tengri_output/dsl" ] bumpalo = [ "dep:bumpalo" ] [dependencies] -dizzle = { workspace = true } - +tengri_core = { workspace = true } tengri_input = { workspace = true } tengri_output = { workspace = true } +tengri_dsl = { workspace = true, optional = true } atomic_float = { workspace = true } better-panic = { workspace = true } bumpalo = { workspace = true, optional = true } crossterm = { workspace = true } +konst = { workspace = true } palette = { workspace = true } quanta = { workspace = true } rand = { workspace = true } @@ -26,4 +30,5 @@ unicode-width = { workspace = true } [dev-dependencies] tengri = { workspace = true, features = [ "dsl" ] } +tengri_dsl = { workspace = true } tengri_proc = { workspace = true } diff --git a/tui/src/lib.rs b/tui/src/lib.rs deleted file mode 100644 index 39d3c59..0000000 --- a/tui/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![feature(type_changing_struct_update, trait_alias)] -pub use ::{tengri_input, tengri_output, ratatui, crossterm, palette, better_panic}; -pub(crate) use ::{ - dizzle::*, - tengri_input::*, - tengri_output::*, - atomic_float::AtomicF64, - std::{io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}}, - better_panic::{Settings, Verbosity}, - palette::{*, convert::*, okhsl::*}, - ratatui::{ - prelude::{Color, Style, Buffer}, - style::Modifier, - backend::{Backend, CrosstermBackend, ClearType}, - layout::{Size, Rect}, - buffer::Cell - }, - crossterm::{ - ExecutableCommand, - terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, - event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, - } -}; -mod tui_engine; pub use self::tui_engine::*; -mod tui_content; pub use self::tui_content::*; -#[cfg(test)] mod tui_test { - use crate::*; - #[test] fn test_tui_engine () -> Usually<()> { - //use std::sync::{Arc, RwLock}; - struct TestComponent(String); - impl Content for TestComponent { - fn content (&self) -> impl Draw { - Some(self.0.as_str()) - } - } - impl Handle for TestComponent { - 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)); - //engine.run(&state)?; - Ok(()) - } - //#[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.rs b/tui/src/tui.rs new file mode 100644 index 0000000..853d99d --- /dev/null +++ b/tui/src/tui.rs @@ -0,0 +1,79 @@ +#![feature(type_changing_struct_update)] +#![feature(trait_alias)] +#[cfg(test)] mod tui_test; +mod tui_engine; pub use self::tui_engine::*; +mod tui_content; pub use self::tui_content::*; +pub use ::{ + tengri_input, + tengri_output, + ratatui, + crossterm, + palette, + better_panic +}; +pub(crate) use ::{ + tengri_core::*, + tengri_input::*, + tengri_output::*, + atomic_float::AtomicF64, + std::{io::{stdout, Stdout}, sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}}, + better_panic::{Settings, Verbosity}, + palette::{*, convert::*, okhsl::*}, + ratatui::{ + prelude::{Color, Style, Buffer}, + style::Modifier, + backend::{Backend, CrosstermBackend, ClearType}, + layout::{Size, Rect}, + buffer::Cell + }, + crossterm::{ + ExecutableCommand, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, + event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, + } +}; + +#[cfg(feature = "dsl")] use tengri_dsl::*; +#[cfg(feature = "dsl")] +pub fn evaluate_output_expression_tui <'a, S> ( + state: &S, mut output: &mut TuiOut, expr: impl DslExpr + 'a +) -> Usually where + S: View + + for<'b>DslNs<'b, bool> + + for<'b>DslNs<'b, u16> + + for<'b>DslNs<'b, Color> +{ + // See `tengri_output::evaluate_output_expression` + let head = expr.head()?; + let mut frags = head.src()?.unwrap_or_default().split("/"); + let args = expr.tail(); + let arg0 = args.head(); + let tail0 = args.tail(); + let arg1 = tail0.head(); + let tail1 = tail0.tail(); + let arg2 = tail1.head(); + match frags.next() { + + Some("text") => if let Some(src) = args?.src()? { output.place(&src) }, + + Some("fg") => { + let arg0 = arg0?.expect("fg: expected arg 0 (color)"); + output.place(&Tui::fg( + DslNs::::from(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), + Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), + )) + }, + + Some("bg") => { + let arg0 = arg0?.expect("bg: expected arg 0 (color)"); + output.place(&Tui::bg( + DslNs::::from(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), + Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), + )) + }, + + _ => return Ok(false) + + }; + Ok(true) +} diff --git a/tui/src/tui_engine.rs b/tui/src/tui_engine.rs index 52a84a0..4fce13a 100644 --- a/tui/src/tui_engine.rs +++ b/tui/src/tui_engine.rs @@ -102,47 +102,3 @@ impl TuiRun for Arc> { Ok(()) } } - -#[cfg(feature = "dsl")] -pub fn evaluate_output_expression_tui <'a, S> ( - state: &S, mut output: &mut TuiOut, expr: impl DslExpr + 'a -) -> Usually where - S: View - + for<'b>DslNs<'b, bool> - + for<'b>DslNs<'b, u16> - + for<'b>DslNs<'b, Color> -{ - // See `tengri_output::evaluate_output_expression` - let head = expr.head()?; - let mut frags = head.src()?.unwrap_or_default().split("/"); - let args = expr.tail(); - let arg0 = args.head(); - let tail0 = args.tail(); - let arg1 = tail0.head(); - let tail1 = tail0.tail(); - let arg2 = tail1.head(); - match frags.next() { - - Some("text") => if let Some(src) = args?.src()? { output.place(&src) }, - - Some("fg") => { - let arg0 = arg0?.expect("fg: expected arg 0 (color)"); - output.place(&Tui::fg( - DslNs::::from(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), - )) - }, - - Some("bg") => { - let arg0 = arg0?.expect("bg: expected arg 0 (color)"); - output.place(&Tui::bg( - DslNs::::from(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), - Thunk::new(move|output: &mut TuiOut|state.view(output, &arg1).unwrap()), - )) - }, - - _ => return Ok(false) - - }; - Ok(true) -} diff --git a/tui/src/tui_engine/tui_event.rs b/tui/src/tui_engine/tui_event.rs index 1245a30..dbc0a98 100644 --- a/tui/src/tui_engine/tui_event.rs +++ b/tui/src/tui_engine/tui_event.rs @@ -1,5 +1,5 @@ use crate::*; -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(pub Event); +#[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) diff --git a/tui/src/tui_test.rs b/tui/src/tui_test.rs index e69de29..dbfeefd 100644 --- a/tui/src/tui_test.rs +++ b/tui/src/tui_test.rs @@ -0,0 +1,35 @@ +use crate::*; +#[test] fn test_tui_engine () -> Usually<()> { + //use std::sync::{Arc, RwLock}; + struct TestComponent(String); + impl Content for TestComponent { + fn content (&self) -> impl Draw { + Some(self.0.as_str()) + } + } + impl Handle for TestComponent { + 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)); + //engine.run(&state)?; + Ok(()) +} +//#[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 )); +//}