Compare commits

...

2 commits

Author SHA1 Message Date
1ef898ac32 remove DslKey
Some checks failed
/ build (push) Has been cancelled
2025-08-17 19:32:43 +03:00
ddd162f225 dsl: exp -> expr, sym -> word 2025-08-17 19:24:06 +03:00
8 changed files with 308 additions and 326 deletions

View file

@ -95,7 +95,7 @@ or configuration statements, and look like this:
## implementation notes ## implementation notes
### `DslExp` trait behavior ### `DslExpr` trait behavior
this is the trait which differentiates "a thing" from this is the trait which differentiates "a thing" from
"a thing that is many things". "a thing that is many things".

View file

@ -18,7 +18,6 @@ pub(crate) use self::DslError::*;
mod dsl_conv; pub use self::dsl_conv::*; mod dsl_conv; pub use self::dsl_conv::*;
mod dsl_ns; pub use self::dsl_ns::*; mod dsl_ns; pub use self::dsl_ns::*;
mod dsl_src; pub use self::dsl_src::*; mod dsl_src; pub use self::dsl_src::*;
mod dsl_type; pub use self::dsl_type::*;
#[cfg(test)] mod dsl_test; #[cfg(test)] mod dsl_test;
/// DSL-specific result type. /// DSL-specific result type.
pub type DslResult<T> = Result<T, DslError>; pub type DslResult<T> = Result<T, DslError>;
@ -40,3 +39,246 @@ pub enum DslError {
#[error("end reached")] #[error("end reached")]
End End
} }
fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
Ok(x.transpose()?.flatten())
}
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 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),
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)
},
}
}
#[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<D: Dsl> $T for D {}
/// 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")) { return Err(e) }
Ok(Some(str_range(source, start, start + length)))
}
}
}
/// Find a start and length corresponding to a syntax token.
pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> {
match $seek_start(source) {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some(start)) => match $seek_length(str_from(source, start)) {
Ok(Some(length)) => Ok(Some((start, length))),
Ok(None) => Ok(None),
Err(e) => Err(e),
},
}
}
pub const fn $seek_start ($source1: &str) -> DslPerhaps<usize> $body1
pub const fn $seek_length ($source2: &str) -> DslPerhaps<usize> $body2
});
pub const fn is_expr_start (c: char) -> bool { c == '(' }
pub const fn is_expr_end (c: char) -> bool { c == ')' }
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)
}
});
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")) { return Err(e) }
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_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)
}
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()))
}
});
pub const fn is_text_start (c: char) -> bool { matches!(c, '"') }
pub const fn is_text_end (c: char) -> bool { matches!(c, '"') }
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!(DslNum {
fn num (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(num_peek_only))}
} {
pub const fn num_peek [generated];
pub const fn num_peek_only [generated];
pub const fn num_seek [generated];
pub const fn num_seek_start (src) {
for_each!((i, c) in char_indices(src) =>
if is_digit(c) { return Ok(Some(i)); } else
if !is_space(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(None)
}
pub const fn num_seek_length (src) {
for_each!((i, c) in char_indices(src) =>
if is_num_end(c) { return Ok(Some(i)) } else
if !is_digit(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(None)
}
});
pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') }
pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') }
pub const fn to_number (digits: &str) -> Result<usize, DslError> {
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<usize, DslError> {
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")))
})
}

View file

@ -10,18 +10,18 @@ use crate::*;
// Special form for numeric types // Special form for numeric types
(num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { (num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => {
impl<'t> DslNs<'t, $Type> for $State { impl<'t> DslNs<'t, $Type> for $State {
const SYMS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> = const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@sym ($state: $State) -> $Type { $pat => $body }}),*]); DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> = const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]);
fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<$Type> { fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<$Type> {
if let Ok(Some(src)) = dsl.src() { if let Ok(Some(src)) = dsl.src() {
if let Ok(Some(num)) = src.num() { if let Ok(num) = to_number(src) {
Ok(Some(to_number(num)? as $Type)) Ok(Some(num as $Type))
} else if let Ok(Some(src)) = src.sym() { } else if let Ok(Some(src)) = src.word() {
self.from_sym(src) self.from_word(src)
} else { } else {
self.from_exp(src) self.from_expr(src)
} }
} else { } else {
Ok(None) Ok(None)
@ -36,17 +36,17 @@ use crate::*;
// Regular form for single type // Regular form for single type
(|$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => { (|$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => {
impl<'t> DslNs<'t, $Type> for $State { impl<'t> DslNs<'t, $Type> for $State {
const SYMS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> = const WORDS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@sym ($state: $State) -> $Type { $pat => $body }}),*]); DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> = const EXPRS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]); DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]);
} }
}; };
// Symbols only. // Symbols only.
(@sym ($state:ident: $State:ty) -> $Type:ty { (@word ($state:ident: $State:ty) -> $Type:ty {
$sym:literal => $body:expr $word:literal => $body:expr
}) => { }) => {
($sym, |$state|Ok(Some($body))) ($word, |$state|Ok(Some($body)))
}; };
// Expression handlers only. // Expression handlers only.
(@exp ($state:ident: $State:ty) -> $Type:ty { (@exp ($state:ident: $State:ty) -> $Type:ty {
@ -54,17 +54,21 @@ use crate::*;
}) => { ($head, |$state, tail: &str|{ }) => { ($head, |$state, tail: &str|{
$( $(
let head = tail.head()?.unwrap_or_default(); let head = tail.head()?.unwrap_or_default();
let tail = tail.tail()?.unwrap_or_default();
let $arg: $ty = if let Some(arg) = $state.from(&head)? { let $arg: $ty = if let Some(arg) = $state.from(&head)? {
arg arg
} else { } else {
return Err(format!("missing argument: {}", stringify!($arg)).into()) return Err(format!("{}: missing argument: {} ({}); got: {tail}",
$head,
stringify!($arg),
stringify!($Type),
).into())
}; };
let tail = tail.tail()?.unwrap_or_default();
)* )*
Ok(Some($body)) Ok(Some($body))
}) }; }) };
// Nothing else in symbols. // Nothing else in symbols.
(@sym ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => { (@word ($state:ident: $State:ty) -> $Type:ty { $pat:tt => $body:expr }) => {
("", |_|Ok(None)) // FIXME don't emit at all ("", |_|Ok(None)) // FIXME don't emit at all
}; };
// Nothing else in expression handlers. // Nothing else in expression handlers.
@ -75,42 +79,45 @@ use crate::*;
); );
pub trait DslNs<'t, T: 't>: 't { pub trait DslNs<'t, T: 't>: 't {
/// Known symbols.
const SYMS: DslNsMap<'t, fn (&'t Self)->Perhaps<T>> = DslNsMap::new(&[]);
/// Known expressions.
const EXPS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps<T>> = DslNsMap::new(&[]);
/// Resolve a symbol if known.
fn from_sym <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(dsl) = dsl.sym()? {
for (sym, get) in Self::SYMS.0 { if dsl == *sym { return get(self) } }
}
return Ok(None)
}
/// Resolve an expression if known.
fn from_exp <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(exp) = dsl.exp()? {
for (key, value) in Self::EXPS.0.iter() {
if exp.head() == Ok(Some(key)) { return value(self, exp.tail()?.unwrap_or("")) }
}
}
return Ok(None)
}
/// Resolve an expression or symbol. /// Resolve an expression or symbol.
fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> { fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Ok(Some(src)) = dsl.src() { if let Ok(Some(src)) = dsl.src() {
if let Ok(Some(src)) = src.sym() { if let Ok(Some(src)) = src.word() {
self.from_sym(src) self.from_word(src)
} else { } else {
self.from_exp(src) self.from_expr(src)
} }
} else { } else {
Ok(None) Ok(None)
} }
} }
/// Resolve a symbol if known.
fn from_word <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(dsl) = dsl.word()? {
for (word, get) in Self::WORDS.0 { if dsl == *word { return get(self) } }
}
return Ok(None)
}
/// Resolve an expression if known.
fn from_expr <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(head) = dsl.expr().head()? {
for (key, value) in Self::EXPRS.0.iter() {
if head == *key {
return value(self, dsl.expr().tail()?.unwrap_or(""))
}
}
}
return Ok(None)
}
/// Known symbols.
const WORDS: DslNsMap<'t, fn (&'t Self)->Perhaps<T>> = DslNsMap::new(&[]);
/// Known expressions.
const EXPRS: DslNsMap<'t, fn (&'t Self, &str)->Perhaps<T>> = DslNsMap::new(&[]);
} }
/// Namespace mapping. /// Namespace mapping.
pub struct DslNsMap<'t, T: 't>(pub &'t [(&'t str, T)]); #[derive(Debug)]
impl<'t, T: 't> DslNsMap<'t, T> { pub struct DslNsMap<'t, T: Debug + 't>(pub &'t [(&'t str, T)]);
impl<'t, T: Debug + 't> DslNsMap<'t, T> {
/// Populate a namespace. /// Populate a namespace.
pub const fn new (data: &'t [(&'t str, T)]) -> Self { pub const fn new (data: &'t [(&'t str, T)]) -> Self {
Self(data) // TODO build search index Self(data) // TODO build search index

View file

@ -1,267 +0,0 @@
use crate::*;
fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
Ok(x.transpose()?.flatten())
}
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 peek (src: &str) -> DslPerhaps<&str> {
Ok(Some(if let Ok(Some(exp)) = exp_peek(src) { exp } else
if let Ok(Some(sym)) = sym_peek(src) { sym } else
if let Ok(Some(num)) = num_peek(src) { num } else
if let Ok(Some(text)) = text_peek(src) { text } else
if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) }
else { return Ok(None) }))
}
pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> {
Ok(Some(if let Ok(Some(exp)) = exp_seek(src) { exp } else
if let Ok(Some(sym)) = sym_seek(src) { sym } else
if let Ok(Some(num)) = num_seek(src) { num } else
if let Ok(Some(text)) = text_seek(src) { text } else
if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) }
else { return Ok(None) }))
}
pub const fn peek_tail (src: &str) -> DslPerhaps<&str> {
match seek(src) {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some((start, length))) => {
let tail = str_range(src, start + length, src.len());
for_each!((_i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) });
Ok(None)
},
}
}
#[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<D: Dsl> $T for D {}
/// 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")) { return Err(e) }
Ok(Some(str_range(source, start, start + length)))
}
}
}
/// Find a start and length corresponding to a syntax token.
pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> {
match $seek_start(source) {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some(start)) => match $seek_length(str_from(source, start)) {
Ok(Some(length)) => Ok(Some((start, length))),
Ok(None) => Ok(None),
Err(e) => Err(e),
},
}
}
pub const fn $seek_start ($source1: &str) -> DslPerhaps<usize> $body1
pub const fn $seek_length ($source2: &str) -> DslPerhaps<usize> $body2
});
pub const fn is_exp_start (c: char) -> bool { c == '(' }
pub const fn is_exp_end (c: char) -> bool { c == ')' }
dsl_type!(DslExp {
fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))}
fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))}
fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))}
/// my other car is a cdr :<
fn each (&self, mut cb: impl FnMut(&str)->Usually<()>) -> Usually<()> {
Ok(if let Some(head) = self.head()? {
cb(head)?;
if let Some(tail) = self.tail()? {
tail.each(cb)?;
}
})
}
} {
pub const fn exp_peek [generated];
pub const fn exp_peek_only [generated];
pub const fn exp_seek [generated];
pub const fn exp_seek_start (src) {
for_each!((i, c) in char_indices(src) =>
if is_exp_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 exp_seek_length (src) {
let mut depth = 0;
for_each!((i, c) in char_indices(src) =>
if is_exp_start(c) { depth += 1; } else
if is_exp_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)
}
});
pub const fn exp_peek_inner (src: &str) -> DslPerhaps<&str> {
match exp_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 exp_peek_inner_only (src: &str) -> DslPerhaps<&str> {
match exp_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("exp_peek_inner_only")) { return Err(e) }
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_sym_start (c: char) -> bool { is_sym_char(c) }
pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/'|'@'|':') }
pub const fn is_sym_end (c: char) -> bool { is_space(c) || is_exp_end(c) }
dsl_type!(DslSym {
fn sym (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(sym_peek_only))}
} {
pub const fn sym_peek [generated];
pub const fn sym_peek_only [generated];
pub const fn sym_seek [generated];
pub const fn sym_seek_start (src) {
for_each!((i, c) in char_indices(src) => if
is_sym_start(c) { return Ok(Some(i)) } else
if !is_space(c) { return Err(Unexpected(c, Some(i), Some("sym_seek_start"))) });
Ok(None)
}
pub const fn sym_seek_length (src) {
for_each!((i, c) in char_indices(src) =>
if is_sym_end(c) { return Ok(Some(i)) } else
if !is_sym_char(c) { return Err(Unexpected(c, Some(i), Some("sym_seek_length"))) });
Ok(Some(src.len()))
}
});
pub const fn is_text_start (c: char) -> bool { matches!(c, '"') }
pub const fn is_text_end (c: char) -> bool { matches!(c, '"') }
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)
}
});
pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|('a'..='z')) }
pub const fn is_key_char (c: char) -> bool { is_key_start(c) || matches!(c, '0'..='9'|'-') }
pub const fn is_key_end (c: char) -> bool { !is_key_char(c) }
dsl_type!(DslKey {
fn key (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(key_peek_only)) }
} {
pub const fn key_peek [generated];
pub const fn key_peek_only [generated];
pub const fn key_seek [generated];
pub const fn key_seek_start (src) {
for_each!((i, c) in char_indices(src) =>
if is_key_start(c) { return Ok(Some(i)) } else
if !is_space(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(None)
}
pub const fn key_seek_length (src) {
for_each!((i, c) in char_indices(src) =>
if is_key_end(c) { return Ok(Some(i)) } else
if !is_key_char(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(Some(src.len()))
}
});
dsl_type!(DslNum {
fn num (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(num_peek_only))}
} {
pub const fn num_peek [generated];
pub const fn num_peek_only [generated];
pub const fn num_seek [generated];
pub const fn num_seek_start (src) {
for_each!((i, c) in char_indices(src) =>
if is_digit(c) { return Ok(Some(i)); } else
if !is_space(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(None)
}
pub const fn num_seek_length (src) {
for_each!((i, c) in char_indices(src) =>
if is_num_end(c) { return Ok(Some(i)) } else
if !is_digit(c) { return Err(Unexpected(c, Some(i), None)) });
Ok(None)
}
});
pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') }
pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') }
pub const fn to_number (digits: &str) -> Result<usize, DslError> {
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<usize, DslError> {
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")))
})
}

View file

@ -1,5 +1,5 @@
use crate::*; use crate::*;
use std::{sync::Arc, collections::BTreeMap, path::{Path, PathBuf}, fs::{exists, read_to_string}}; use std::{sync::Arc, collections::BTreeMap, path::PathBuf};
/// A collection of input bindings. /// A collection of input bindings.
/// ///
/// Each contained layer defines a mapping from input event to command invocation /// Each contained layer defines a mapping from input event to command invocation

View file

@ -511,7 +511,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
///// The syntagm `(when :condition :content)` corresponds to a [When] layout element. ///// The syntagm `(when :condition :content)` corresponds to a [When] layout element.
//impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> { //impl<S, A> FromDsl<S> for When<A> where bool: FromDsl<S>, A: FromDsl<S> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExp>) -> Perhaps<Self> { //fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("when", |_, tail|Ok(Some(Self( //source.exp_match("when", |_, tail|Ok(Some(Self(
//FromDsl::<S>::provide(state, //FromDsl::<S>::provide(state,
//tail.nth(0, ||"no condition".into())?, ||"no condition".into())?, //tail.nth(0, ||"no condition".into())?, ||"no condition".into())?,
@ -522,7 +522,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
//} //}
///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element. ///// The syntagm `(either :condition :content1 :content2)` corresponds to an [Either] layout element.
//impl<S, A, B> FromDsl<S> for Either<A, B> where S: Eval<Ast, bool> + Eval<Ast, A> + Eval<Ast, B> { //impl<S, A, B> FromDsl<S> for Either<A, B> where S: Eval<Ast, bool> + Eval<Ast, A> + Eval<Ast, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExp>) -> Perhaps<Self> { //fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("either", |_, tail|Ok(Some(Self( //source.exp_match("either", |_, tail|Ok(Some(Self(
//state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?, //state.eval(tail.nth(0, ||"no condition")?, ||"no condition")?,
//state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?, //state.eval(tail.nth(1, ||"no content 1")?, ||"no content 1")?,
@ -533,7 +533,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
///// The syntagm `(align/* :content)` corresponds to an [Align] layout element, ///// The syntagm `(align/* :content)` corresponds to an [Align] layout element,
///// where `*` specifies the direction of the alignment. ///// where `*` specifies the direction of the alignment.
//impl<S, A> FromDsl<S> for Align<A> where S: Eval<Option<Ast>, A> { //impl<S, A> FromDsl<S> for Align<A> where S: Eval<Option<Ast>, A> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExp>) -> Perhaps<Self> { //fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("align/", |head, tail|Ok(Some(match head { //source.exp_match("align/", |head, tail|Ok(Some(match head {
//"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")), //"c" => Self::c(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
//"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")), //"x" => Self::x(state.eval(tail.nth(0, ||"no content")?, ||"no content")),
@ -553,7 +553,7 @@ transform_xy_unit!("padding/x" "padding/y" "padding/xy"|self: Padding, area|{
///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element, ///// The syntagm `(bsp/* :content1 :content2)` corresponds to a [Bsp] layout element,
///// where `*` specifies the direction of the split. ///// where `*` specifies the direction of the split.
//impl<S, A, B> FromDsl<S> for Bsp<A, B> where S: Eval<Option<Ast>, A> + Eval<Option<Ast>, B> { //impl<S, A, B> FromDsl<S> for Bsp<A, B> where S: Eval<Option<Ast>, A> + Eval<Option<Ast>, B> {
//fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExp>) -> Perhaps<Self> { //fn try_provide (state: &S, source: &DslVal<impl DslStr, impl DslExpr>) -> Perhaps<Self> {
//source.exp_match("bsp/", |head, tail|Ok(Some(match head { //source.exp_match("bsp/", |head, tail|Ok(Some(match head {
//"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), //"n" => Self::n(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),
//"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")), //"s" => Self::s(tail.nth(0, ||"no content 1"), tail.nth(1, ||"no content 2")),

View file

@ -87,10 +87,10 @@ impl ExposeImpl {
/// Generated by [tengri_proc::expose]. /// Generated by [tengri_proc::expose].
impl ::tengri::dsl::FromDsl<#state> for #t { impl ::tengri::dsl::FromDsl<#state> for #t {
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> { fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
match dsl.key()? { match dsl.word()? {
Some("true") => Ok(Some(true)), Some("true") => Ok(Some(true)),
Some("false") => Ok(Some(false)), Some("false") => Ok(Some(false)),
_ => match dsl.sym()? { #(#variants)* _ => Ok(None) } _ => match dsl.word()? { #(#variants)* _ => Ok(None) }
} }
} }
} }
@ -102,7 +102,7 @@ impl ExposeImpl {
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> { fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
match dsl.num()? { match dsl.num()? {
Some(n) => Ok(Some(n.parse::<#t>()?)), Some(n) => Ok(Some(n.parse::<#t>()?)),
_ => match dsl.sym()? { #(#variants)* _ => Ok(None) } _ => match dsl.word()? { #(#variants)* _ => Ok(None) }
} }
} }
} }
@ -112,7 +112,7 @@ impl ExposeImpl {
/// Generated by [tengri_proc::expose]. /// Generated by [tengri_proc::expose].
impl ::tengri::dsl::FromDsl<#state> for #t { impl ::tengri::dsl::FromDsl<#state> for #t {
fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> { fn from_dsl (state: &#state, dsl: &impl Dsl) -> Perhaps<Self> {
match dsl.sym()? { #(#variants)* _ => Ok(None) } match dsl.word()? { #(#variants)* _ => Ok(None) }
} }
} }
} }

View file

@ -18,14 +18,14 @@ pub struct TuiKey(Option<KeyCode>, KeyModifiers);
impl TuiKey { impl TuiKey {
const SPLIT: char = '/'; const SPLIT: char = '/';
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> { pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
if let Some(symbol) = dsl.sym()? { if let Some(word) = dsl.word()? {
let symbol = symbol.trim(); let word = word.trim();
Ok(if symbol == ":char" { Ok(if word == ":char" {
Self(None, KeyModifiers::NONE) Self(None, KeyModifiers::NONE)
} else if symbol.chars().nth(0) == Some('@') { } else if word.chars().nth(0) == Some('@') {
let mut key = None; let mut key = None;
let mut modifiers = KeyModifiers::NONE; let mut modifiers = KeyModifiers::NONE;
let mut tokens = symbol[1..].split(Self::SPLIT).peekable(); let mut tokens = word[1..].split(Self::SPLIT).peekable();
while let Some(token) = tokens.next() { while let Some(token) = tokens.next() {
if tokens.peek().is_some() { if tokens.peek().is_some() {
match token { match token {
@ -47,7 +47,7 @@ impl TuiKey {
} }
Self(key, modifiers) Self(key, modifiers)
} else { } else {
return Err(format!("TuiKey: unexpected: {symbol}").into()) return Err(format!("TuiKey: unexpected: {word}").into())
}) })
} else { } else {
return Err(format!("TuiKey: unspecified").into()) return Err(format!("TuiKey: unspecified").into())