Compare commits

...

3 commits

Author SHA1 Message Date
ca4138e365 dsl: exp -> expr, sym -> word
Some checks failed
/ build (push) Has been cancelled
2025-08-16 17:04:28 +03:00
cf253c28f9 dsl: modularize 2025-08-16 16:28:24 +03:00
4fc0db5777 dsl eval and ns again 2025-08-16 14:34:41 +03:00
10 changed files with 278 additions and 271 deletions

View file

@ -22,6 +22,7 @@ version = "0.13.0"
edition = "2024" edition = "2024"
[workspace.dependencies] [workspace.dependencies]
anyhow = { version = "1.0" }
atomic_float = { version = "1" } atomic_float = { version = "1" }
better-panic = { version = "0.3.0" } better-panic = { version = "0.3.0" }
const_panic = { version = "0.2.12", features = [ "derive" ] } const_panic = { version = "0.2.12", features = [ "derive" ] }

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

@ -17,42 +17,37 @@ pub(crate) use ::{
pub(crate) use self::DslError::*; 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_type; mod dsl_src; pub use self::dsl_src::*;
#[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>;
/// DSL-specific optional result type. /// DSL-specific optional result type.
pub type DslPerhaps<T> = Result<Option<T>, DslError>; pub type DslPerhaps<T> = Result<Option<T>, DslError>;
// Some things that can be DSL source:
impl Dsl for String { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
impl Dsl for Arc<str> { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } }
// Designates a string as parsable DSL.
flex_trait!(Dsl: Debug + Send + Sync + Sized { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } });
impl<D: Dsl> Dsl for Option<D> {
fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })}
}
impl<D: Dsl> Dsl for Result<D, DslError> {
fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}}
}
/// DSL-specific error codes. /// DSL-specific error codes.
#[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)] #[derive(Error, Debug, Copy, Clone, PartialEq, PanicFmt)]
pub enum DslError { pub enum DslError {
#[error("parse failed: not implemented")] Unimplemented, #[error("parse failed: not implemented")]
#[error("parse failed: empty")] Empty, Unimplemented,
#[error("parse failed: incomplete")] Incomplete, #[error("parse failed: empty")]
#[error("parse failed: unexpected character '{0}'")] Unexpected(char, Option<usize>, Option<&'static str>), Empty,
#[error("parse failed: error #{0}")] Code(u8), #[error("parse failed: incomplete")]
#[error("end reached")] End Incomplete,
#[error("parse failed: unexpected character '{0}'")]
Unexpected(char, Option<usize>, Option<&'static str>),
#[error("parse failed: error #{0}")]
Code(u8),
#[error("end reached")]
End
}
fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> {
Ok(x.transpose()?.flatten())
} }
fn ok_flat <T> (x: Option<DslPerhaps<T>>) -> DslPerhaps<T> { Ok(x.transpose()?.flatten()) }
pub const fn is_space (c: char) -> bool { pub const fn is_space (c: char) -> bool {
matches!(c, ' '|'\n'|'\r'|'\t') matches!(c, ' '|'\n'|'\r'|'\t')
} }
pub const fn no_trailing_non_space (src: &str, offset: usize, context: Option<&'static str>) -> DslResult<()> { 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) { 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 { return Err(Unexpected(c, Some(offset + i), if let Some(context) = context {
Some(context) Some(context)
@ -61,20 +56,17 @@ pub const fn no_trailing_non_space (src: &str, offset: usize, context: Option<&'
})) }))
})) }))
} }
pub const fn peek (src: &str) -> DslPerhaps<&str> { pub const fn peek (src: &str) -> DslPerhaps<&str> {
Ok(Some(if let Ok(Some(exp)) = exp_peek(src) { exp } else Ok(Some(if let Ok(Some(expr)) = expr_peek(src) { expr } else
if let Ok(Some(sym)) = sym_peek(src) { sym } else if let Ok(Some(word)) = word_peek(src) { word } else
if let Ok(Some(key)) = key_peek(src) { key } else
if let Ok(Some(num)) = num_peek(src) { num } else if let Ok(Some(num)) = num_peek(src) { num } else
if let Ok(Some(text)) = text_peek(src) { text } 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) } if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) }
else { return Ok(None) })) else { return Ok(None) }))
} }
pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> {
Ok(Some(if let Ok(Some(exp)) = exp_seek(src) { exp } else Ok(Some(if let Ok(Some(expr)) = expr_seek(src) { expr } else
if let Ok(Some(sym)) = sym_seek(src) { sym } else if let Ok(Some(word)) = word_seek(src) { word } else
if let Ok(Some(key)) = key_seek(src) { key } else
if let Ok(Some(num)) = num_seek(src) { num } else if let Ok(Some(num)) = num_seek(src) { num } else
if let Ok(Some(text)) = text_seek(src) { text } 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) } if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) }
@ -92,10 +84,56 @@ pub const fn peek_tail (src: &str) -> DslPerhaps<&str> {
} }
} }
pub const fn is_exp_start (c: char) -> bool { c == '(' } #[macro_export] macro_rules! dsl_type (($T:ident { $($trait:tt)* } {
pub const fn is_exp_end (c: char) -> bool { c == ')' } pub const fn $peek:ident $($_1:tt)?;
dsl_type!(DslExp { pub const fn $peek_only:ident $($_2:tt)?;
fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} 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 head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))}
fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))} fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail))}
/// my other car is a cdr :< /// my other car is a cdr :<
@ -108,20 +146,20 @@ dsl_type!(DslExp {
}) })
} }
} { } {
pub const fn exp_peek [generated]; pub const fn expr_peek [generated];
pub const fn exp_peek_only [generated]; pub const fn expr_peek_only [generated];
pub const fn exp_seek [generated]; pub const fn expr_seek [generated];
pub const fn exp_seek_start (src) { pub const fn expr_seek_start (src) {
for_each!((i, c) in char_indices(src) => for_each!((i, c) in char_indices(src) =>
if is_exp_start(c) { return Ok(Some(i)) } else if is_expr_start(c) { return Ok(Some(i)) } else
if !is_space(c) { return Err(Unexpected(c, Some(i), Some("expected expression start"))) }); if !is_space(c) { return Err(Unexpected(c, Some(i), Some("expected expression start"))) });
Ok(None) Ok(None)
} }
pub const fn exp_seek_length (src) { pub const fn expr_seek_length (src) {
let mut depth = 0; let mut depth = 0;
for_each!((i, c) in char_indices(src) => for_each!((i, c) in char_indices(src) =>
if is_exp_start(c) { depth += 1; } else if is_expr_start(c) { depth += 1; } else
if is_exp_end(c) { if is_expr_end(c) {
if depth == 0 { if depth == 0 {
return Err(Unexpected(c, Some(i), Some("expected expression end"))) return Err(Unexpected(c, Some(i), Some("expected expression end")))
} else if depth == 1 { } else if depth == 1 {
@ -134,8 +172,8 @@ dsl_type!(DslExp {
} }
}); });
pub const fn exp_peek_inner (src: &str) -> DslPerhaps<&str> { pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> {
match exp_peek(src) { match expr_peek(src) {
Ok(Some(peeked)) => { Ok(Some(peeked)) => {
let len = peeked.len(); let len = peeked.len();
let start = if len > 0 { 1 } else { 0 }; let start = if len > 0 { 1 } else { 0 };
@ -145,12 +183,12 @@ pub const fn exp_peek_inner (src: &str) -> DslPerhaps<&str> {
} }
} }
pub const fn exp_peek_inner_only (src: &str) -> DslPerhaps<&str> { pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> {
match exp_seek(src) { match expr_seek(src) {
Err(e) => Err(e), Err(e) => Err(e),
Ok(None) => Ok(None), Ok(None) => Ok(None),
Ok(Some((start, length))) => { Ok(Some((start, length))) => {
if let Err(e) = no_trailing_non_space(src, start + length, Some("exp_peek_inner_only")) { return Err(e) } 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 peeked = str_range(src, start, start + length);
let len = peeked.len(); let len = peeked.len();
let start = if len > 0 { 1 } else { 0 }; let start = if len > 0 { 1 } else { 0 };
@ -159,25 +197,26 @@ pub const fn exp_peek_inner_only (src: &str) -> DslPerhaps<&str> {
} }
} }
pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } pub const fn is_word_char (c: char) -> bool {
pub const fn is_sym_char (c: char) -> bool { is_sym_start(c) || matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/') } matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/'|'@'|':')
pub const fn is_sym_end (c: char) -> bool { is_space(c) || matches!(c, ')') } }
dsl_type!(DslSym { pub const fn is_word_end (c: char) -> bool {
fn sym (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(sym_peek_only))} 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 sym_peek [generated]; pub const fn word_peek [generated];
pub const fn sym_peek_only [generated]; pub const fn word_peek_only [generated];
pub const fn sym_seek [generated]; pub const fn word_seek [generated];
pub const fn sym_seek_start (src) { pub const fn word_seek_start (src) {
for_each!((i, c) in char_indices(src) => if for_each!((i, c) in char_indices(src) => if
is_sym_start(c) { return Ok(Some(i)) } else is_word_char(c) { return Ok(Some(i)) } else
if !is_space(c) { return Err(Unexpected(c, Some(i), Some("sym_seek_start"))) }); if !is_space(c) { return Err(Unexpected(c, Some(i), Some("word_seek_start"))) });
Ok(None) Ok(None)
} }
pub const fn sym_seek_length (src) { pub const fn word_seek_length (src) {
for_each!((i, c) in char_indices(src) => for_each!((i, c) in char_indices(src) => if !is_word_char(c) { return Ok(Some(i)) });
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())) Ok(Some(src.len()))
} }
}); });
@ -247,7 +286,7 @@ dsl_type!(DslNum {
}); });
pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } 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 is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') }
pub const fn to_number <D: Dsl> (digits: &str) -> Result<usize, DslError> { pub const fn to_number (digits: &str) -> Result<usize, DslError> {
let mut iter = char_indices(digits); let mut iter = char_indices(digits);
let mut value = 0; let mut value = 0;
while let Some(((_, c), next)) = iter.next() { while let Some(((_, c), next)) = iter.next() {

View file

@ -1,74 +1,125 @@
use crate::*; use crate::*;
/// Define a namespace:
/// Namespace where keys are symbols. #[macro_export] macro_rules! dsl_ns (
pub trait DslSymNs<'t, T: 't>: 't { // Special form for numeric types
/// Known symbold. (num |$state:ident : $State: ty| $($($num:lifetime)? $Type:ty $(=> { $(
const SYMS: DslNs<'t, fn (&'t Self)->Perhaps<T>> = DslNs(&[]); $pat:tt => $body:expr
/// Resolve a symbol if known. ),* $(,)? })?;)+) => {
fn from_sym <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> { $(dsl_ns!(num |$state: $State| -> $($num)? $Type { $( $($pat => $body),* )? });)+
if let Some(dsl) = dsl.sym()? { };
for (sym, get) in Self::SYMS.0 { if dsl == *sym { return get(self) } } // Special form for numeric types
} (num |$state:ident : $State: ty| -> $Type:ty { $( $pat:tt => $body:expr ),* $(,)? }) => {
return Ok(None) impl<'t> DslNs<'t, $Type> for $State {
} const SYMS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
} DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPS: DslNsMap<'t, fn (&'t $State, &str)->Perhaps<$Type>> =
/// Define a symbol namespace. DslNsMap::new(&[$(dsl_ns!{@exp ($state: $State) -> $Type { $pat => $body }}),*]);
#[macro_export] macro_rules! dsl_sym_ns ( fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<$Type> {
(|$state:ident:$State:ty| -> $Type:ty {$($lit:literal => $exp:expr),* $(,)?})=>{ if let Ok(Some(src)) = dsl.src() {
impl<'t> DslSymNs<'t, $Type> for $State { if let Ok(Some(num)) = src.num() {
const SYMS: DslNs<'t, fn (&'t $State)->Perhaps<$Type>> = DslNs(&[ Ok(Some(to_number(num)? as $Type))
$(($lit, |$state: &$State|Ok(Some($exp)))),* } else if let Ok(Some(src)) = src.word() {
]); self.from_word(src)
} } else {
}); self.from_exp(src)
}
/// Namespace where the keys are heads of expressions. } else {
pub trait DslExpNs<'t, T: 't>: 't { Ok(None)
/// Known expressions. }
const EXPS: DslNs<'t, fn (&'t Self, &str)->Perhaps<T>> = DslNs(&[]);
/// Resolve an expression if known.
fn from_exp <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Some(exp) = dsl.exp()? {
//panic!("exp\n{:?}\n{:?}\n{:?}\n{:?}",
//exp.src(), exp.head(), exp.head().src(), Self::EXPS.0);
for (key, value) in Self::EXPS.0.iter() {
println!("{key} {:?}", exp.head());
if exp.head() == Ok(Some(key)) { return value(self, exp.tail()?.unwrap_or("")) }
} }
} }
};
// 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 SYMS: DslNsMap<'t, fn (&'t $State)->Perhaps<$Type>> =
DslNsMap::new(&[$(dsl_ns!{@word ($state: $State) -> $Type { $pat => $body }}),*]);
const EXPS: DslNsMap<'t, fn (&'t $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 $arg: $ty = if let Some(arg) = $state.from(&head)? {
arg
} else {
return Err(format!("{}: missing argument: {}",
stringify!($head),
stringify!($arg)
).into())
};
let tail = tail.tail()?.unwrap_or_default();
)*
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
};
);
pub trait DslNs<'t, T: 't>: 't {
/// Resolve an expression or symbol.
fn from <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
if let Ok(Some(src)) = dsl.src() {
if let Ok(Some(src)) = src.word() {
self.from_word(src)
} else {
self.from_exp(src)
}
} else {
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::SYMS.0 { if dsl == *word { return get(self) } }
}
return Ok(None) return Ok(None)
} }
} /// Resolve an expression if known.
fn from_exp <D: Dsl> (&'t self, dsl: D) -> Perhaps<T> {
/// Define an expression namespace. if let Some(head) = dsl.expr().head()? {
#[macro_export] macro_rules! dsl_exp_ns ( for (key, value) in Self::EXPS.0.iter() {
(|$state:ident:$State:ty|->$Type:ty { $( ( if head == *key {
$key:literal $(/ $sub:ident : $Sub:ty)? $(, $arg:ident:$ty:ty)* $(, ..$tail:ident)? return value(self, dsl.expr().tail()?.unwrap_or(""))
) => $body:expr),* $(,)? }) => { }
impl<'t> DslExpNs<'t, $Type> for $State { }
const EXPS: DslNs<'t, fn (&'t $State, &str)->Perhaps<$Type>> = DslNs(&[ $(
($key, |$state: &$State, mut tail: &str|{
$(let $arg: $ty = if let Some(val) = dsl_next(&mut tail)? {
val
} else {
return Err(format!("missing arg: {}", stringify!($arg)).into())
};)*
$(let $tail = tail;)?
Ok(Some($body))
})
),* ]);
} }
}); panic!("{dsl:?}");
return Ok(None)
pub fn dsl_next <T> (dsl: &mut impl Dsl) -> DslPerhaps<T> { }
todo!() /// 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(&[]);
}
/// Namespace mapping.
#[derive(Debug)]
pub struct DslNsMap<'t, T: Debug + 't>(pub &'t [(&'t str, T)]);
impl<'t, T: Debug + 't> DslNsMap<'t, T> {
/// Populate a namespace.
pub const fn new (data: &'t [(&'t str, T)]) -> Self {
Self(data) // TODO build search index
}
} }
/// Namespace.
///
/// Currently linearly searched.
/// Maybe it's better to use a trie.
/// I don't think there's a `const` trie in Rust?
/// Oughtta be.
pub struct DslNs<'t, T: 't>(pub &'t [(&'t str, T)]);

21
dsl/src/dsl_src.rs Normal file
View file

@ -0,0 +1,21 @@
use crate::*;
// Designates a string as parsable DSL.
flex_trait!(Dsl: Debug + Send + Sync + Sized {
fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") }
});
// Some things that can be DSL source:
impl Dsl for String {
fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) }
}
impl Dsl for Arc<str> {
fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) }
}
impl<'s> Dsl for &'s str {
fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) }
}
impl<D: Dsl> Dsl for Option<D> {
fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })}
}
impl<D: Dsl> Dsl for Result<D, DslError> {
fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}}
}

View file

@ -1,51 +0,0 @@
use crate::*;
#[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 {}
dsl_peek_seek!($peek, $peek_only, $seek, $seek_start, $seek_length);
pub const fn $seek_start ($source1: &str) -> DslPerhaps<usize> $body1
pub const fn $seek_length ($source2: &str) -> DslPerhaps<usize> $body2
});
#[macro_export] macro_rules! dsl_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{
/// Find a slice corrensponding to a syntax token.
pub const fn $peek (source: &str) -> DslPerhaps<&str> {
match $seek(source) {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))),
}
}
/// Find a slice corrensponding to a syntax token
/// but return an error if it isn't the only thing
/// in the source.
pub const fn $peek_only (source: &str) -> DslPerhaps<&str> {
match $seek(source) {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some((start, length))) => {
if let Err(e) = no_trailing_non_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),
},
}
}
});

View file

@ -1,9 +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};
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
type EventMapImpl<E, C> = BTreeMap<E, Vec<Binding<C>>>;
/// 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
@ -14,15 +10,33 @@ type EventMapImpl<E, C> = BTreeMap<E, Vec<Binding<C>>>;
/// When the first non-conditional or true conditional binding is executed, /// When the first non-conditional or true conditional binding is executed,
/// that .event()binding's value is returned. /// that .event()binding's value is returned.
#[derive(Debug)] #[derive(Debug)]
pub struct EventMap<E, C>(pub EventMapImpl<E, C>); pub struct EventMap<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// An input binding. /// An input binding.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Binding<C> { pub struct Binding<C> {
pub command: C, pub commands: Arc<[C]>,
pub condition: Option<Condition>, pub condition: Option<Condition>,
pub description: Option<Arc<str>>, pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>, pub source: Option<Arc<PathBuf>>,
} }
impl<C> Binding<C> {
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let command: Option<C> = None;
let condition: Option<Condition> = None;
let description: Option<Arc<str>> = None;
let source: Option<Arc<PathBuf>> = None;
if let Some(command) = command {
Ok(Self { commands: [command].into(), condition, description, source })
} else {
Err(format!("no command in {dsl:?}").into())
}
}
}
/// Input bindings are only returned if this evaluates to true /// Input bindings are only returned if this evaluates to true
#[derive(Clone)] #[derive(Clone)]
pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>); pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
@ -59,72 +73,4 @@ impl<E: Clone + Ord, C> EventMap<E, C> {
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
.flatten() .flatten()
} }
/// Create event map from path to text file.
pub fn load_from_path <'s> (
&'s mut self, path: impl AsRef<Path>
) -> Usually<&'s mut Self> where Self: DslInto<C> + DslInto<E> {
if exists(path.as_ref())? {
let source = read_to_string(&path)?;
let path: Arc<PathBuf> = Arc::new(path.as_ref().into());
self.load_from_source(&source, &Some(&path))
} else {
return Err(format!("(e5) not found: {:?}", path.as_ref()).into())
}
}
/// Create event map from DSL tokenizer.
pub fn load_from_source (
&mut self, dsl: impl Dsl, path: &Option<&Arc<PathBuf>>
) -> Usually<&mut Self> where Self: DslInto<C> + DslInto<E> {
dsl.each(|dsl|self.load_from_source_one(&dsl, path).map(move|_|()))?;
Ok(self)
}
/// Load one event binding into the event map.
pub fn load_from_source_one <'s> (
&'s mut self, dsl: impl Dsl, path: &Option<&Arc<PathBuf>>
) -> Usually<&'s mut Self> where Self: DslInto<C> + DslInto<E> {
if let Some(exp) = dsl.head()?.exp()?
&& let Some(sym) = exp.head()?.sym()?
&& let Some(tail) = exp.tail()?
{
let event = self.dsl_into_or_else(&sym, ||panic!())?;
let command = self.dsl_into_or_else(&tail, ||panic!())?;
Ok(self.add(event, Binding {
command, condition: None, description: None, source: path.cloned()
}))
} else {
Err(format!("unexpected: {:?}", dsl.head()?).into())
}
}
//})Ok(if let Some(sym) = dsl.head()?.exp()?.head()?.sym()? {
//if let Some(tail) = dsl.head()?.exp()?.tail()? {
//let event: E = sym.into();
//let binding: Binding<C> = Binding { command: tail.into(), condition: None, description: None, source: None };
//if let Some(bindings) = map.0.get_mut(&event) {
//bindings.push(binding);
//} else {
//map.0.insert(event, vec![binding]);
//}
//} else {
//panic!("empty binding: {}", dsl.head()?.exp()?.unwrap_or_default())
//}
//} else if let Some(ref text) = dsl.text()? {
//map.0.extend(Self::from_path(PathBuf::from(text))?.0);
//} else {
//return Err(format!("unexpected: {dsl:?}").into())
//}));
//Ok(map)
//}
}
impl<C> Binding<C> {
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let command: Option<C> = None;
let condition: Option<Condition> = None;
let description: Option<Arc<str>> = None;
let source: Option<Arc<PathBuf>> = None;
if let Some(command) = command {
Ok(Self { command, condition, description, source })
} else {
Err(format!("no command in {dsl:?}").into())
}
}
} }

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

@ -90,7 +90,7 @@ impl ExposeImpl {
match dsl.key()? { match dsl.key()? {
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())