diff --git a/core/src/core_macros.rs b/core/src/core_macros.rs index 0da5094..a8fdda4 100644 --- a/core/src/core_macros.rs +++ b/core/src/core_macros.rs @@ -39,6 +39,35 @@ //$(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_unsized ( + ($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,)+)? _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 ( diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index 823dc74..90cbaed 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -3,6 +3,7 @@ #![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; @@ -15,60 +16,58 @@ pub(crate) use ::{ 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; -mod dsl_macros; + // Trait that designates any string-like as potentially parsable DSL. -flex_trait!(Dsl: Debug + Send + Sync + Sized { +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>; -/// 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 -} + 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(num)) = num_peek(src) { num } else if let Ok(Some(text)) = text_peek(src) { text } else if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) } else { return Ok(None) })) } + pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { Ok(Some(if let Ok(Some(expr)) = expr_seek(src) { expr } else if let Ok(Some(word)) = word_seek(src) { word } else - //if let Ok(Some(num)) = num_seek(src) { num } else if let Ok(Some(text)) = text_seek(src) { text } else if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) } else { return Ok(None) })) } + pub const fn peek_tail (src: &str) -> DslPerhaps<&str> { match seek(src) { Err(e) => Err(e), @@ -80,6 +79,7 @@ pub const fn peek_tail (src: &str) -> DslPerhaps<&str> { }, } } + pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> { match expr_peek(src) { Ok(Some(peeked)) => { @@ -90,6 +90,7 @@ pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> { e => e } } + pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> { match expr_seek(src) { Err(e) => Err(e), @@ -106,9 +107,11 @@ pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> { }, } } + 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<()> { @@ -120,15 +123,35 @@ pub const fn no_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 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; @@ -141,6 +164,7 @@ pub const fn to_number (digits: &str) -> Result { } Ok(value) } + pub const fn to_digit (c: char) -> Result { Ok(match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, @@ -149,142 +173,58 @@ pub const fn to_digit (c: char) -> Result { }) } -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())) - } -}); - -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) - } -}); - -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) - } -}); - -fn ok_flat (x: Option>) -> DslPerhaps { +pub(crate) fn ok_flat (x: Option>) -> DslPerhaps { Ok(x.transpose()?.flatten()) } -/// Namespace mapping. -#[derive(Debug)] pub struct DslNsMap<'t, T: Debug + 't>(pub &'t [(&'t str, T)]); -/// Namespace mapping for [DslWord]s. -pub type DslWords<'t, T, U> = DslNsMap<'t, fn (&'t T)->Perhaps>; -/// Namespace mapping for [DslExpr]s. -pub type DslExprs<'t, T, U> = DslNsMap<'t, fn (&'t T, &str)->Perhaps>; -impl<'t, T: Debug + 't> DslNsMap<'t, T> { - /// Populate a namespace with pre-existing values. - pub const fn new (data: &'t [(&'t str, T)]) -> Self { Self(data) /* TODO a search trie */ } -} - - -pub trait DslNs<'t, T: 't>: 't { - /// Known symbols. - const WORDS: DslWords<'t, Self, T> = DslNsMap::new(&[]); - /// Known expressions. - const EXPRS: DslExprs<'t, Self, T> = DslNsMap::new(&[]); - /// Resolve an expression or symbol. - fn from (&'t self, dsl: impl Dsl) -> 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) +#[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), + }, } } - /// Resolve as literal if valid. - fn from_literal (&self, _: impl Dsl) -> Perhaps { - Ok(None) + /// 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))), + } } - /// Resolve a symbol if known. - fn from_word (&'t self, dsl: impl Dsl) -> Perhaps { - if let Some(dsl) = dsl.word()? { - for (key, get) in Self::WORDS.0.iter() { - if dsl == *key { - let value = get(self); - return value + /// 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))) } } } - return Ok(None) } - /// Resolve an expression if known. - fn from_expr (&'t self, dsl: impl Dsl) -> Perhaps { - if let Some(expr) = dsl.expr()? { - for (key, get) in Self::EXPRS.0.iter() { - if Some(*key) == expr.head()? { - return get(self, &expr.tail()?.unwrap_or("")) - } - } - } - return Ok(None) - } -} +}); #[deprecated] /// `T` + [Dsl] -> `Self`. 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..d22115a --- /dev/null +++ b/dsl/src/dsl_expr.rs @@ -0,0 +1,70 @@ +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) + }),* )? ]; +}); diff --git a/dsl/src/dsl_macros.rs b/dsl/src/dsl_macros.rs deleted file mode 100644 index f4460f3..0000000 --- a/dsl/src/dsl_macros.rs +++ /dev/null @@ -1,153 +0,0 @@ -#[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))) - } - } - } - } -}); - -#[macro_export] macro_rules!dsl_words( - (|$state:ident|->$Type:ty$({ - $($word:literal => $body:expr),* $(,)? - })?)=>{ - const WORDS: DslWords<'t, Self, $Type> = DslNsMap::new(&[$( - $(($word, move|$state: &Self|{Ok(Some($body))})),* - )? ]); - }); - -#[macro_export] macro_rules!dsl_exprs( - (|$state:ident|->$Type:ty$({ - $($name:literal ($($arg:ident:$ty:ty),* $(,)?) => $body:expr),* $(,)? - })?)=>{ - const EXPRS: DslExprs<'t, Self, $Type> = DslNsMap::new(&[$( - $(($name, |$state: &Self, tail: &str|{ - $( - 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)) - })),* - )? ]); - }); - -/// Define a DSL namespace that provides values to words and expressions. -#[macro_export] macro_rules! dsl_ns ( - // Special form for numeric types - (num |$state:ident : $State: ty| $($($num:lifetime)? $Type:ty $(=> { $( - $pat:tt => $body:expr - ),* $(,)? })?;)+) => { - $(dsl_ns!(num |$state: $State| -> $($num)? $Type { $( $($pat => $body),* )? });)+ - }; - // Special form for numeric types - (num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { - impl<'t> DslNs<'t, $Type> for $State { - const WORDS: DslWords<'t, $State, $Type> = DslNsMap::new(&[ - $(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]); - const EXPRS: DslExprs<'t, $State, $Type> = DslNsMap::new(&[ - $(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); - fn from_literal (&self, dsl: impl Dsl) -> Perhaps<$Type> { - Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as $Type) - } else { - None - }) - } - } - }; - // A namespace may resolve one or more types. - (|$state:ident : $State: ty| $($Type:ty $(=> { $( $pat:tt => $body:expr ),* $(,)? })? ;)+) => { - $(dsl_ns!(|$state: $State| -> $Type { $( $($pat => $body),* )? });)+ - }; - // Regular form for single type - (|$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { - impl<'t> DslNs<'t, $Type> for $State { - const WORDS: DslNsMap<'t, fn (&$State)->Perhaps<$Type>> = - DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]); - const EXPRS: DslNsMap<'t, fn (&$State, &str)->Perhaps<$Type>> = - DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); - } - }; - // Symbols only. - (@word ($state:ident: $State:ty) -> $Type:ty { - $word:literal => $body:expr - }) => { - ($word, |$state|Ok(Some($body))) - }; - // Expression handlers only. - (@exp ($state:ident: $State:ty) -> $Type:ty { - ($head:literal $(,$arg:ident:$ty:ty)* $(,)*) => $body:expr - }) => { ($head, |$state, tail: &str|{ - $( - 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!("{}: missing argument: {} ({}); got: {tail}", - $head, - stringify!($arg), - stringify!($Type), - ).into()) - }; - )* - Ok(Some($body)) - }) }; - // Nothing else in symbols. - (@word ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => { - ("", |_|Ok(None)) // FIXME don't emit at all - }; - // Nothing else in expression handlers. - (@exp ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => { - ("", |_, _|Ok(None)) // FIXME don't emit at all - }; -); diff --git a/dsl/src/dsl_ns.rs b/dsl/src/dsl_ns.rs new file mode 100644 index 0000000..b9c81f8 --- /dev/null +++ b/dsl/src/dsl_ns.rs @@ -0,0 +1,120 @@ +use crate::*; + +pub trait DslNs<'a, T: 'a>: 'a { + /// Known symbols. + const WORDS: DslWords<'a, Self, T> = &[]; + /// Known expressions. + const EXPRS: DslExprs<'a, Self, 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) + } + /// 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) + } + /// Resolve an expression if known. + fn from_expr (&'a self, dsl: impl DslExpr + 'a) -> Perhaps { + let head = dsl.head()?; + for (key, get) in Self::EXPRS.iter() { + if Some(*key) == head { + return get(self, dsl.tail()?.unwrap_or("")) + } + } + return Ok(None) + } +} + +/// Define a DSL namespace that provides values to words and expressions. +#[macro_export] macro_rules! dsl_ns ( + // Special form for numeric types + (num |$state:ident : $State: ty| $($($num:lifetime)? $Type:ty $(=> { $( + $pat:tt => $body:expr + ),* $(,)? })?;)+) => { + $(dsl_ns!(num |$state: $State| -> $($num)? $Type { $( $($pat => $body),* )? });)+ + }; + // Special form for numeric types + (num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { + impl<'a> DslNs<'a, $Type> for $State { + const WORDS: DslWords<'a, $State, $Type> = &[ + $(dsl_ns!{@word 'a ($state: $State) -> $Type { $pat => $body }}),*]; + const EXPRS: DslExprs<'a, $State, $Type> = &[ + $(dsl_ns!{@exp 'a ($state: $State) -> $Type { $pat => $body }}),*]; + fn from_literal (&self, dsl: impl Dsl) -> Perhaps<$Type> { + Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as $Type) + } else { + None + }) + } + } + }; + // A namespace may resolve one or more types. + (|$state:ident : $State: ty| $($Type:ty $(=> { $( $pat:tt => $body:expr ),* $(,)? })? ;)+) => { + $(dsl_ns!(|$state: $State| -> $Type { $( $($pat => $body),* )? });)+ + }; + // Regular form for single type + (|$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { + impl<'a> DslNs<'a, $Type> for $State { + const WORDS: DslWords<'a, $State, $Type> = + &[$(dsl_ns!{@word 'a ($state: $State) -> $Type { $pat => $body }}),*]; + const EXPRS: DslExprs<'a, $State, $Type> = + &[$(dsl_ns!{@exp 'a ($state: $State) -> $Type { $pat => $body }}),*]; + } + }; + // Symbols only. + (@word $l:lifetime ($state:ident: $State:ty) -> $Type:ty { + $word:literal => $body:expr + }) => {{ + let get: GetDslWord<$l, Self, $Type> = (|$state: &$State|Ok(Some($body))); + ($word, get) + }}; + // Expression handlers only. + (@exp $l:lifetime ($state:ident: $State:ty) -> $Type:ty { + ($head:literal $(,$arg:ident:$ty:ty)* $(,)*) => $body:expr + }) => {{ + let get: GetDslExpr<$l, Self, $Type> = |$state, tail: &str|{ + $( + 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!("{}: missing argument: {} ({}); got: {tail}", + $head, + stringify!($arg), + stringify!($Type), + ).into()) + }; + )* + Ok(Some($body)) + }; + ($head, get) + }}; + // Nothing else in symbols. + (@word $l:lifetime ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => { + ("", ((|_|Ok(None)) as GetDslWord<$l, Self, $Type>)) // FIXME don't emit at all + }; + // Nothing else in expression handlers. + (@exp $l:lifetime ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => { + ("", ((|_, _|Ok(None)) as GetDslExpr<$l, Self, $Type>)) // FIXME don't emit at all + }; +); 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..48b5c59 --- /dev/null +++ b/dsl/src/dsl_word.rs @@ -0,0 +1,32 @@ +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) + }),* )?]; +}); diff --git a/output/src/layout_stack.rs b/output/src/layout_stack.rs index 4e45e4b..95f8c82 100644 --- a/output/src/layout_stack.rs +++ b/output/src/layout_stack.rs @@ -6,7 +6,7 @@ pub struct Stack<'x, E, F> { direction: Direction, callback: F } -impl<'x, E, F: Fn(&dyn Fn(&dyn Render)) + 'x> Stack<'x, E, F> { +impl<'x, E, F: Fn(&mut dyn FnMut(&dyn Render)) + 'x> Stack<'x, E, F> { pub fn new (direction: Direction, callback: F) -> Self { Self { direction, callback, __: Default::default(), } } @@ -29,15 +29,15 @@ impl<'x, E, F: Fn(&dyn Fn(&dyn Render)) + 'x> Stack<'x, E, F> { Self::new(West, callback) } } -impl<'x, E: Output, F: Fn(&dyn Fn(&dyn Render)) + 'x> Content for Stack<'x, E, F> { +impl<'x, E: Output, F: Fn(&mut dyn FnMut(&dyn Render)) + 'x> Content for Stack<'x, E, F> { fn layout (&self, to: E::Area) -> E::Area { let state = StackLayoutState::::new(self.direction, to); - let adder = &|component: &dyn Render|{ + let mut adder = |component: &dyn Render|{ let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow(); let [_, _, w, h] = component.layout([x, y, w_remaining, h_remaining].into()).xywh(); state.borrow_mut().grow(w, h); }; - (self.callback)(&adder); + (self.callback)(&mut adder); let StackLayoutState { w_used, h_used, .. } = *state.borrow(); match self.direction { North | West => { todo!() }, @@ -48,13 +48,13 @@ impl<'x, E: Output, F: Fn(&dyn Fn(&dyn Render)) + 'x> Content for Stack<'x fn render (&self, to: &mut E) { let state = StackLayoutState::::new(self.direction, to.area()); let to = Rc::new(RefCell::new(to)); - let adder = &|component: &dyn Render|{ + let mut adder = |component: &dyn Render|{ let StackLayoutState { x, y, w_remaining, h_remaining, .. } = *state.borrow(); let layout = component.layout([x, y, w_remaining, h_remaining].into()); state.borrow_mut().grow(layout.w(), layout.h()); to.borrow_mut().place(layout, component); }; - (self.callback)(&adder); + (self.callback)(&mut adder); } }