diff --git a/dsl/README.md b/dsl/README.md index a5bcf22..009a2f9 100644 --- a/dsl/README.md +++ b/dsl/README.md @@ -100,16 +100,15 @@ or configuration statements, and look like this: this is the trait which differentiates "a thing" from "a thing that is many things". -|source |key|exp |head |tail | -|---------------|---|-------|---------|---------------| -|`a` |`a`|e0 |`a` |None | -|`(a)` |e1 |`a` |`(a)` |None | -|`a b c` |e2 |e0 |`a` |`b c` | -|`(a b c)` |e1 |`a b c`|`(a b c)`| | -|`(a b c) d e` |e1 |e3 |`(a b c)`|`d e` | -|`a (b c d) e f`|e1 |e0 |`a` |`(b c d) e f` | +|source |key|exp |head |tail | +|-----------------------|---|-------|---------|---------------| +|`a` |`a`|E0 |`a` |none | +|`(a)` |E1 |`a` |`a` |none | +|`a b c` |E2 |E0 |`a` |`b c` | +|`(a b c)` |E0 |`a b c`|`(a b c)`|none | +|`(a b c) d e f` |E0 |E2 |`(a b c)`|`d e f` | +|`a (b c d) e f` |E2 |E0 |`a` |`(b c d) e f` | -* e0: Unexpected 'a' -* e1: Unexpected '(' -* e2: Unexpected 'b' -* e3: Unexpected 'd' +* **E0**: Expected `(` +* **E1**: Unexpected `(` +* **E2**: Trailing characters diff --git a/dsl/src/dsl.rs b/dsl/src/dsl.rs index c43e2f1..51a5250 100644 --- a/dsl/src/dsl.rs +++ b/dsl/src/dsl.rs @@ -6,40 +6,58 @@ extern crate const_panic; use const_panic::PanicFmt; use std::fmt::Debug; -pub(crate) use std::{error::Error, sync::Arc}; -pub(crate) use konst::{iter::for_each, string::{str_from, str_range, char_indices}}; +pub(crate) use std::error::Error; +pub(crate) use std::sync::Arc; +pub(crate) use konst::iter::for_each; +pub(crate) use konst::string::{str_from, str_range, char_indices}; pub(crate) use thiserror::Error; pub(crate) use ::tengri_core::*; pub(crate) use self::DslError::*; mod dsl_conv; pub use self::dsl_conv::*; +mod dsl_parse; +pub(crate) use self::dsl_parse::*; +pub mod parse { pub use crate::dsl_parse::*; } #[cfg(test)] mod dsl_test; flex_trait!(Dsl: Debug + Send + Sync + Sized { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } }); -impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } -impl<'s> Dsl for &'s str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } +impl Dsl for Arc { + fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } +} +impl<'s> Dsl for &'s str { + fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } +} impl Dsl for Option { - fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} + 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)}} + fn src (&self) -> DslPerhaps<&str> { + match self { Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e) } + } } -impl DslExp for D {} pub trait DslExp: Dsl { - fn exp (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(exp_peek_inner_only))} - fn head (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek))} - fn tail (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(peek_tail(self.head())))} +impl DslExp for D {} +pub trait DslExp: Dsl { + fn exp (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(exp_peek_inner)) } + fn head (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(peek)) } + fn tail (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(peek_tail(self.head()))) } } -impl DslText for D {} pub trait DslText: Dsl { - fn text (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(text_peek_only))} +impl DslSym for D {} +pub trait DslSym: Dsl { + fn sym (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(sym_peek_only)) } } -impl DslSym for D {} pub trait DslSym: Dsl { - fn sym (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(sym_peek_only))} +impl DslKey for D {} +pub trait DslKey: Dsl { + fn key (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(key_peek_only)) } } -impl DslKey for D {} pub trait DslKey: Dsl { - fn key (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(key_peek_only))} +impl DslText for D {} +pub trait DslText: Dsl { + fn text (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(text_peek_only)) } } -impl DslNum for D {} pub trait DslNum: Dsl { - fn num (&self) -> DslPerhaps<&str> {ok_flat(self.src()?.map(num_peek_only))} +impl DslNum for D {} +pub trait DslNum: Dsl { + fn num (&self) -> DslPerhaps<&str> { ok_flat(self.src()?.map(num_peek_only)) } } /// DSL-specific result type. pub type DslResult = Result; @@ -60,7 +78,7 @@ pub enum DslError { let mut $next: Option> = dsl.next()?.map(Into::into); let mut rest: Option> = dsl.rest()?.map(Into::into); loop { - if let Some($next) = $next { $body } else { break }; + if let Some($next) = $next { $body; } else { break } if let Some(next) = rest { $next = next.next()?.map(Into::into); rest = next.rest()?.map(Into::into); @@ -80,220 +98,3 @@ fn peek_tail <'a> (head: DslPerhaps<&'a str>) -> impl Fn(&'a str)->DslPerhaps<&' e => e } } -macro_rules! def_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ - /// Find a slice corrensponding to a syntax token. - pub const fn $peek (source: &str) -> DslPerhaps<&str> { - match $seek(source) { - Err(e) => Err(e), - Ok(None) => Ok(None), - Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), - } - } - /// Find a slice corrensponding to a syntax token - /// but return an error if it isn't the only thing - /// in the source. - pub const fn $peek_only (source: &str) -> DslPerhaps<&str> { - match $seek(source) { - Err(e) => Err(e), - Ok(None) => Ok(None), - Ok(Some((start, length))) => { - if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } - Ok(Some(str_range(source, start, start + length))) - } - } - } - /// Find a start and length corresponding to a syntax token. - pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { - match $seek_start(source) { - Err(e) => Err(e), - Ok(None) => Ok(None), - Ok(Some(start)) => match $seek_length(str_from(source, start)) { - Ok(Some(length)) => Ok(Some((start, length))), - Ok(None) => Ok(None), - Err(e) => Err(e), - }, - } - } -}); -def_peek_seek!(exp_peek, exp_peek_only, exp_seek, exp_seek_start, exp_seek_length); -pub const fn exp_peek_inner (source: &str) -> DslPerhaps<&str> { - match exp_peek(source) { - Ok(Some(peeked)) => { - let len = peeked.len(); - let start = if len > 0 { 1 } else { 0 }; - Ok(Some(str_range(source, start, start + len.saturating_sub(2)))) - }, - e => e - } -} -pub const fn exp_peek_inner_only (source: &str) -> DslPerhaps<&str> { - match exp_seek(source) { - Err(e) => Err(e), - Ok(None) => Ok(None), - Ok(Some((start, length))) => { - if let Err(e) = no_trailing_non_whitespace(source, start + length) { return Err(e) } - let peeked = str_range(source, start, start + length); - let len = peeked.len(); - let start = if len > 0 { 1 } else { 0 }; - Ok(Some(str_range(peeked, start, start + len.saturating_sub(2)))) - }, - } -} -pub const fn is_exp_start (c: char) -> bool { c == '(' } -pub const fn is_exp_end (c: char) -> bool { c == ')' } -pub const fn exp_seek_start (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_exp_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn exp_seek_length (source: &str) -> DslPerhaps { - let mut depth = 0; - for_each!((i, c) in char_indices(source) => if is_exp_start(c) { - depth += 1; - } else if is_exp_end(c) { - if depth == 0 { - return Err(Unexpected(c)) - } else if depth == 1 { - return Ok(Some(i + 1)) - } else { - depth -= 1; - } - }); - Err(Incomplete) -} -def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); -pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } -pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } -pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn sym_seek_start (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_sym_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn sym_seek_length (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_sym_end(c) { - return Ok(Some(i)) - } else if !is_sym_char(c) { - return Err(Unexpected(c)) - }); - Ok(Some(source.len())) -} -def_peek_seek!(key_peek, key_peek_only, key_seek, key_seek_start, key_seek_length); -pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|('a'..='z')) } -pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } -pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn key_seek_start (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_key_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn key_seek_length (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_key_end(c) { - return Ok(Some(i)) - } else if !is_key_char(c) { - return Err(Unexpected(c)) - }); - Ok(Some(source.len())) -} -def_peek_seek!(text_peek, text_peek_only, text_seek, text_seek_start, text_seek_length); -pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } -pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } -pub const fn text_seek_start (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_text_start(c) { - return Ok(Some(i)) - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn text_seek_length (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_text_end(c) { return Ok(Some(i)) }); - Ok(None) -} -def_peek_seek!(num_peek, num_peek_only, num_seek, num_seek_start, num_seek_length); -pub const fn num_seek_start (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_digit(c) { - return Ok(Some(i)); - } else if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn num_seek_length (source: &str) -> DslPerhaps { - for_each!((i, c) in char_indices(source) => if is_num_end(c) { - return Ok(Some(i)) - } else if !is_digit(c) { - return Err(Unexpected(c)) - }); - Ok(None) -} -pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } -pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } -pub const fn to_number (digits: &str) -> Result { - let mut iter = char_indices(digits); - let mut value = 0; - while let Some(((_, c), next)) = iter.next() { - match to_digit(c) { - Ok(digit) => value = 10 * value + digit, - Err(e) => return Err(e), - } - iter = next; - } - Ok(value) -} -pub const fn to_digit (c: char) -> Result { - Ok(match c { - '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, - '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, - _ => return Err(Unexpected(c)) - }) -} -pub const fn peek (src: &str) -> DslPerhaps<&str> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_peek(src) => exp, - _ if let Ok(Some(sym)) = sym_peek(src) => sym, - _ if let Ok(Some(key)) = key_peek(src) => key, - _ if let Ok(Some(num)) = num_peek(src) => num, - _ if let Ok(Some(text)) = text_peek(src) => text, - _ => { - for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} -pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { - Ok(Some(match () { - _ if let Ok(Some(exp)) = exp_seek(src) => exp, - _ if let Ok(Some(sym)) = sym_seek(src) => sym, - _ if let Ok(Some(key)) = key_seek(src) => key, - _ if let Ok(Some(num)) = num_seek(src) => num, - _ if let Ok(Some(text)) = text_seek(src) => text, - _ => { - for_each!((_, c) in char_indices(src) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - return Ok(None) - } - })) -} -pub const fn is_whitespace (c: char) -> bool { - matches!(c, ' '|'\n'|'\r'|'\t') -} -pub const fn no_trailing_non_whitespace (source: &str, offset: usize) -> DslResult<()> { - let tail = str_range(source, offset, source.len()); - for_each!((_, c) in char_indices(tail) => if !is_whitespace(c) { - return Err(Unexpected(c)) - }); - Ok(()) -} diff --git a/dsl/src/dsl_parse.rs b/dsl/src/dsl_parse.rs new file mode 100644 index 0000000..bd8208d --- /dev/null +++ b/dsl/src/dsl_parse.rs @@ -0,0 +1,232 @@ +use crate::*; + +macro_rules! def_peek_seek(($peek:ident, $peek_only:ident, $seek:ident, $seek_start:ident, $seek_length:ident)=>{ + /// Find a slice corrensponding to a syntax token. + pub const fn $peek (source: &str) -> DslPerhaps<&str> { + match $seek(source) { + Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } + /// 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) { + Ok(Some((start, length))) => { + let remaining = source.len().saturating_sub(start + length); + let tail = str_range(source, start + length, remaining); + for_each!((_, c) in char_indices(tail) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(Some(str_range(source, start, start + length))) + } + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } + /// Find a start and length corresponding to a syntax token. + pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { + match $seek_start(source) { + Ok(Some(start)) => match $seek_length(str_from(source, start)) { + Ok(Some(length)) => Ok(Some((start, length))), + Ok(None) => Ok(None), + Err(e) => Err(e), + }, + Ok(None) => Ok(None), + Err(e) => Err(e) + } + } +}); + +pub const fn no_trailing_non_whitespace (source: &str, offset: usize) -> DslResult<()> { + let tail = str_range(source, offset, source.len().saturating_sub(offset)); + for_each!((_, c) in char_indices(tail) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(()) +} + +def_peek_seek!(exp_peek, exp_peek_only, exp_seek, exp_seek_start, exp_seek_length); +pub const fn exp_peek_inner (source: &str) -> DslPerhaps<&str> { + match exp_peek(source) { + Ok(Some(peeked)) => { + let len = peeked.len(); + let start = if len > 0 { 1 } else { 0 }; + Ok(Some(str_range(source, start, start + len.saturating_sub(2)))) + }, + e => e + } +} +pub const fn exp_peek_inner_only (source: &str) -> DslPerhaps<&str> { + match exp_seek(source) { + Err(e) => Err(e), + Ok(None) => Ok(None), + Ok(Some((start, length))) => { + if let Err(e) = no_trailing_non_whitespace(source, start) { + return Err(e) + } + let start = if length > 0 { 1 } else { 0 }; + Ok(Some(str_range(source, start, start + length.saturating_sub(2)))) + }, + } +} +pub const fn is_exp_start (c: char) -> bool { c == '(' } +pub const fn is_exp_end (c: char) -> bool { c == ')' } +pub const fn exp_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_exp_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn exp_seek_length (source: &str) -> DslPerhaps { + let mut depth = 0; + for_each!((i, c) in char_indices(source) => if is_exp_start(c) { + depth += 1; + } else if is_exp_end(c) { + if depth == 0 { + return Err(Unexpected(c)) + } else if depth == 1 { + return Ok(Some(i + 1)) + } else { + depth -= 1; + } + }); + Err(Incomplete) +} + +def_peek_seek!(sym_peek, sym_peek_only, sym_seek, sym_seek_start, sym_seek_length); +pub const fn is_sym_start (c: char) -> bool { matches!(c, ':'|'@') } +pub const fn is_sym_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-') } +pub const fn is_sym_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn sym_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_sym_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn sym_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_sym_end(c) { + return Ok(Some(i)) + } else if !is_sym_char(c) { + return Err(Unexpected(c)) + }); + Ok(Some(source.len())) +} + +def_peek_seek!(key_peek, key_peek_only, key_seek, key_seek_start, key_seek_length); +pub const fn is_key_start (c: char) -> bool { matches!(c, '/'|('a'..='z')) } +pub const fn is_key_char (c: char) -> bool { matches!(c, 'a'..='z'|'0'..='9'|'-'|'/') } +pub const fn is_key_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn key_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_key_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn key_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_key_end(c) { + return Ok(Some(i)) + } else if !is_key_char(c) { + return Err(Unexpected(c)) + }); + Ok(Some(source.len())) +} + +def_peek_seek!(text_peek, text_peek_only, text_seek, text_seek_start, text_seek_length); +pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } +pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } +pub const fn text_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_text_start(c) { + return Ok(Some(i)) + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn text_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_text_end(c) { return Ok(Some(i)) }); + Ok(None) +} + +def_peek_seek!(num_peek, num_peek_only, num_seek, num_seek_start, num_seek_length); +pub const fn num_seek_start (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_digit(c) { + return Ok(Some(i)); + } else if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn num_seek_length (source: &str) -> DslPerhaps { + for_each!((i, c) in char_indices(source) => if is_num_end(c) { + return Ok(Some(i)) + } else if !is_digit(c) { + return Err(Unexpected(c)) + }); + Ok(None) +} +pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } +pub const fn is_num_end (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t'|')') } +pub const fn to_number (digits: &str) -> Result { + let mut iter = char_indices(digits); + let mut value = 0; + while let Some(((_, c), next)) = iter.next() { + match to_digit(c) { + Ok(digit) => value = 10 * value + digit, + Err(e) => return Err(e), + } + iter = next; + } + Ok(value) +} +pub const fn to_digit (c: char) -> Result { + Ok(match c { + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, + '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + _ => return Err(Unexpected(c)) + }) +} + +pub const fn peek (mut src: &str) -> DslPerhaps<&str> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_peek(src) => exp, + _ if let Ok(Some(sym)) = sym_peek(src) => sym, + _ if let Ok(Some(key)) = key_peek(src) => key, + _ if let Ok(Some(num)) = num_peek(src) => num, + _ if let Ok(Some(text)) = text_peek(src) => text, + _ => { + for_each!((i, c) in char_indices(src) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} + +pub const fn seek (mut src: &str) -> DslPerhaps<(usize, usize)> { + Ok(Some(match () { + _ if let Ok(Some(exp)) = exp_seek(src) => exp, + _ if let Ok(Some(sym)) = sym_seek(src) => sym, + _ if let Ok(Some(key)) = key_seek(src) => key, + _ if let Ok(Some(num)) = num_seek(src) => num, + _ if let Ok(Some(text)) = text_seek(src) => text, + _ => { + for_each!((i, c) in char_indices(src) => if !is_whitespace(c) { + return Err(Unexpected(c)) + }); + return Ok(None) + } + })) +} + +pub const fn is_whitespace (c: char) -> bool { + matches!(c, ' '|'\n'|'\r'|'\t') +} diff --git a/dsl/src/dsl_test.rs b/dsl/src/dsl_test.rs index 05f0308..be491fe 100644 --- a/dsl/src/dsl_test.rs +++ b/dsl/src/dsl_test.rs @@ -1,6 +1,6 @@ use crate::*; -macro_rules!is_some(($exp:expr, $val:expr)=>{assert_eq!($exp, Ok(Some($val)))};); -macro_rules!is_none(($exp:expr)=>{assert_eq!($exp, Ok(None))};); +macro_rules!is_ok_some(($exp:expr, $val:expr)=>{assert_eq!($exp, Ok(Some($val)))};); +macro_rules!is_ok_none(($exp:expr)=>{assert_eq!($exp, Ok(None))};); macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())}; ($exp:expr, $err:expr)=>{assert_eq!($exp, Err($err))};); #[test] fn test_exp () -> Result<(), DslError> { @@ -25,49 +25,49 @@ macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())}; assert!(!is_key_start(' ')); assert!(is_key_start('f')); - is_some!(key_seek_start("foo"), 0); - is_some!(key_seek_start("foo "), 0); - is_some!(key_seek_start(" foo "), 1); - is_some!(key_seek_length(&" foo "[1..]), 3); - is_some!(key_seek("foo"), (0, 3)); - is_some!(key_peek("foo"), "foo"); - is_some!(key_seek("foo "), (0, 3)); - is_some!(key_peek("foo "), "foo"); - is_some!(key_seek(" foo "), (1, 3)); - is_some!(key_peek(" foo "), "foo"); + is_ok_some!(key_seek_start("foo"), 0); + is_ok_some!(key_seek_start("foo "), 0); + is_ok_some!(key_seek_start(" foo "), 1); + is_ok_some!(key_seek_length(&" foo "[1..]), 3); + is_ok_some!(key_seek("foo"), (0, 3)); + is_ok_some!(key_peek("foo"), "foo"); + is_ok_some!(key_seek("foo "), (0, 3)); + is_ok_some!(key_peek("foo "), "foo"); + is_ok_some!(key_seek(" foo "), (1, 3)); + is_ok_some!(key_peek(" foo "), "foo"); is_err!("(foo)".key()); is_err!("foo".exp()); - is_some!("(foo)".exp(), "foo"); - is_some!("(foo)".head(), "(foo)"); - is_none!("(foo)".tail()); + is_ok_some!("(foo)".exp(), "foo"); + is_ok_some!("(foo)".head(), "(foo)"); + is_ok_none!("(foo)".tail()); - is_some!("(foo bar baz)".exp(), "foo bar baz"); - is_some!("(foo bar baz)".head(), "(foo bar baz)"); - is_none!("(foo bar baz)".tail()); + is_ok_some!("(foo bar baz)".exp(), "foo bar baz"); + is_ok_some!("(foo bar baz)".head(), "(foo bar baz)"); + is_ok_none!("(foo bar baz)".tail()); - is_some!("(foo bar baz)".exp().head(), "foo"); - is_some!("(foo bar baz)".exp().tail(), "bar baz"); - is_some!("(foo bar baz)".exp().tail().head(), "bar"); - is_some!("(foo bar baz)".exp().tail().tail(), "baz"); + is_ok_some!("(foo bar baz)".exp().head(), "foo"); + is_ok_some!("(foo bar baz)".exp().tail(), "bar baz"); + is_ok_some!("(foo bar baz)".exp().tail().head(), "bar"); + is_ok_some!("(foo bar baz)".exp().tail().tail(), "baz"); - is_err!("foo".exp()); - is_some!("foo".key(), "foo"); - is_some!(" foo".key(), "foo"); - is_some!(" foo ".key(), "foo"); + is_ok_none!("foo".exp()); + is_ok_some!("foo".key(), "foo"); + is_ok_some!(" foo".key(), "foo"); + is_ok_some!(" foo ".key(), "foo"); - is_some!(" foo ".head(), "foo"); + assert_eq!(" foo ".head(), Ok(Some("foo"))); //assert_eq!(" foo ".head().head(), Ok(None)); - is_none!(" foo ".head().tail()); - is_none!(" foo ".tail()); - is_none!(" foo ".tail().head()); - is_none!(" foo ".tail().tail()); + assert_eq!(" foo ".head().tail(), Ok(None)); + assert_eq!(" foo ".tail(), Ok(None)); + assert_eq!(" foo ".tail().head(), Ok(None)); + assert_eq!(" foo ".tail().tail(), Ok(None)); assert_eq!(" foo bar ".head(), Ok(Some("foo"))); //assert_eq!(" foo bar ".head().head(), Ok(None)); assert_eq!(" foo bar ".head().tail(), Ok(None)); - assert_eq!(" foo bar ".tail(), Ok(Some(" bar "))); + assert_eq!(" foo bar ".tail(), Ok(Some("bar"))); assert_eq!(" foo bar ".tail().head(), Ok(Some("bar"))); assert_eq!(" foo bar ".tail().tail(), Ok(None)); @@ -79,19 +79,12 @@ macro_rules!is_err(($exp:expr)=>{assert!($exp.is_err())}; assert_eq!(" (foo) (bar) ".head(), Ok(Some("(foo)"))); //assert_eq!(" (foo) (bar) ".head().head(), Ok(Some("foo"))); //assert_eq!(" (foo) (bar) ".head().head().head(), Ok(None)); - is_some!(" (foo) (bar) ".tail(), " (bar) "); - is_some!(" (foo) (bar) ".tail().head(), "(bar)"); - is_some!(" (foo) (bar) ".tail().head().head(), "(bar)"); - is_some!(" (foo) (bar) ".tail().head().exp(), "bar"); - is_some!(" (foo) (bar) ".tail().head().exp().head(), "bar"); + assert_eq!(" (foo) (bar) ".tail(), Ok(Some("(bar)"))); - is_some!(" (foo bar baz) ".head(), "(foo bar baz)"); - is_some!(" (foo bar baz) ".head().head(), "(foo bar baz)"); - is_some!(" (foo bar baz) ".exp(), "foo bar baz"); - is_some!(" (foo bar baz) ".exp().head(), "foo"); - is_some!(" (foo bar baz) ".exp().tail(), "bar baz"); - is_some!(" (foo bar baz) ".exp().tail().head(), "bar"); - is_some!(" (foo bar baz) ".exp().tail().tail(), "baz"); - is_none!(" (foo bar baz) ".tail()); + assert_eq!(" (foo bar baz) ".head(), Ok(Some("(foo bar baz)"))); + assert_eq!(" (foo bar baz) ".head().head(), Ok(Some("foo"))); + assert_eq!(" (foo bar baz) ".head().tail(), Ok(Some("bar baz"))); + assert_eq!(" (foo bar baz) ".head().tail().head(), Ok(Some("bar"))); + assert_eq!(" (foo bar baz) ".head().tail().tail(), Ok(Some("baz"))); Ok(()) } diff --git a/input/src/input_dsl.rs b/input/src/input_dsl.rs index 1f6ac42..bb3c353 100644 --- a/input/src/input_dsl.rs +++ b/input/src/input_dsl.rs @@ -1,9 +1,10 @@ use crate::*; -use std::{sync::Arc, collections::BTreeMap, path::{Path, PathBuf}, fs::{exists, read_to_string}}; + /// Map of each event (e.g. key combination) to /// all command expressions bound to it by /// all loaded input layers. type EventMapImpl = BTreeMap>>; + /// A collection of input bindings. /// /// Each contained layer defines a mapping from input event to command invocation @@ -15,6 +16,7 @@ type EventMapImpl = BTreeMap>>; /// that .event()binding's value is returned. #[derive(Debug)] pub struct EventMap(EventMapImpl); + /// An input binding. #[derive(Debug, Clone)] pub struct Binding { @@ -23,14 +25,18 @@ pub struct Binding { pub description: Option>, pub source: Option>, } + /// Input bindings are only returned if this evaluates to true #[derive(Clone)] pub struct Condition(Arcbool + Send + Sync>>); + impl_debug!(Condition |self, w| { write!(w, "*") }); + /// Default is always empty map regardless if `E` and `C` implement [Default]. impl Default for EventMap { fn default () -> Self { Self(Default::default()) } } + impl EventMap { /// Create a new event map pub fn new () -> Self { @@ -62,7 +68,7 @@ impl EventMap { /// Create event map from path to text file. pub fn from_path > (path: P) -> Usually where E: From> { if exists(path.as_ref())? { - Self::from_source(read_to_string(path)?) + Self::from_source(read_and_leak(path)?) } else { return Err(format!("(e5) not found: {:?}", path.as_ref()).into()) } @@ -111,6 +117,7 @@ impl EventMap { Ok(map) } } + impl Binding { fn from_dsl (dsl: impl Dsl) -> Usually { let mut command: Option = None; @@ -124,9 +131,18 @@ impl Binding { } } } + fn unquote (x: &str) -> &str { let mut chars = x.chars(); chars.next(); //chars.next_back(); chars.as_str() } + +fn read_and_leak (path: impl AsRef) -> Usually<&'static str> { + Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?)) +} + +fn leak (x: impl AsRef) -> &'static str { + Box::leak(x.as_ref().into()) +} diff --git a/input/src/input_test.rs b/input/src/input_test.rs index 24576db..8373435 100644 --- a/input/src/input_test.rs +++ b/input/src/input_test.rs @@ -21,8 +21,8 @@ use crate::*; Ok(()) } -//#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { - //let _keymap = CstIter::new(""); - //Ok(()) -//} +#[cfg(all(test, feature = "dsl"))] #[test] fn test_dsl_keymap () -> Usually<()> { + let _keymap = CstIter::new(""); + Ok(()) +} diff --git a/input/src/lib.rs b/input/src/lib.rs index 800ee2f..f219c64 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,6 +1,13 @@ #![feature(associated_type_defaults)] #![feature(if_let_guard)] + +pub(crate) use std::fmt::Debug; +pub(crate) use std::sync::Arc; +pub(crate) use std::collections::BTreeMap; +pub(crate) use std::path::{Path, PathBuf}; +pub(crate) use std::fs::exists; pub(crate) use tengri_core::*; + mod input_macros; mod input; pub use self::input::*; #[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*; diff --git a/tengri/src/test.rs b/tengri/src/test.rs index 2df3c8a..c782996 100644 --- a/tengri/src/test.rs +++ b/tengri/src/test.rs @@ -1,87 +1,86 @@ -// FIXME -//use crate::*; -//use crate::{dsl::*, input::*, tui::TuiIn}; -//use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; -//use std::cmp::Ordering; +use crate::*; +use crate::{dsl::*, input::*, tui::TuiIn}; +use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}; +use std::cmp::Ordering; -//#[test] fn test_subcommand () -> Usually<()> { - //#[derive(Debug)] struct Event(crossterm::event::Event); - //impl Eq for Event {} - //impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } - //impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } - //impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } - //struct Test { keys: InputMap } +#[test] fn test_subcommand () -> Usually<()> { + #[derive(Debug)] struct Event(crossterm::event::Event); + impl Eq for Event {} + impl PartialEq for Event { fn eq (&self, other: &Self) -> bool { todo!() } } + impl Ord for Event { fn cmp (&self, other: &Self) -> Ordering { todo!() } } + impl PartialOrd for Event { fn partial_cmp (&self, other: &Self) -> Option { None } } + struct Test { keys: InputMap } - //handle!(TuiIn: |self: Test, input|Ok(None));[>if let Some(command) = self.keys.command(self, input) { - //Ok(Some(true)) - //} else { - //Ok(None) - //});*/ + handle!(TuiIn: |self: Test, input|Ok(None));/*if let Some(command) = self.keys.command(self, input) { + Ok(Some(true)) + } else { + Ok(None) + });*/ - //#[tengri_proc::command(Test)] - //impl TestCommand { - //fn do_thing (_state: &mut Test) -> Perhaps { - //Ok(None) - //} - //fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { - //Ok(None) - //} - //fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { - //Ok(command.execute(state)?.map(|command|Self::DoSub { command })) - //} - //} + #[tengri_proc::command(Test)] + impl TestCommand { + fn do_thing (_state: &mut Test) -> Perhaps { + Ok(None) + } + fn do_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + Ok(None) + } + fn do_sub (state: &mut Test, command: TestSubcommand) -> Perhaps { + Ok(command.execute(state)?.map(|command|Self::DoSub { command })) + } + } - //#[tengri_proc::command(Test)] - //impl TestSubcommand { - //fn do_other_thing (_state: &mut Test) -> Perhaps { - //Ok(None) - //} - //fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { - //Ok(None) - //} - //} + #[tengri_proc::command(Test)] + impl TestSubcommand { + fn do_other_thing (_state: &mut Test) -> Perhaps { + Ok(None) + } + fn do_other_thing_arg (_state: &mut Test, _arg: usize) -> Perhaps { + Ok(None) + } + } - //let mut test = Test { - //keys: InputMap::from_source(" - //(@a do-thing) - //(@b do-thing-arg 0) - //(@c do-sub do-other-thing) - //(@d do-sub do-other-thing-arg 0) - //")? - //}; + let mut test = Test { + keys: InputMap::from_source(" + (@a do-thing) + (@b do-thing-arg 0) + (@c do-sub do-other-thing) + (@d do-sub do-other-thing-arg 0) + ")? + }; - ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - ////kind: KeyEventKind::Press, - ////code: KeyCode::Char('a'), - ////modifiers: KeyModifiers::NONE, - ////state: KeyEventState::NONE, - ////})))?); - ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - ////kind: KeyEventKind::Press, - ////code: KeyCode::Char('b'), - ////modifiers: KeyModifiers::NONE, - ////state: KeyEventState::NONE, - ////})))?); - ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - ////kind: KeyEventKind::Press, - ////code: KeyCode::Char('c'), - ////modifiers: KeyModifiers::NONE, - ////state: KeyEventState::NONE, - ////})))?); - ////assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - ////kind: KeyEventKind::Press, - ////code: KeyCode::Char('d'), - ////modifiers: KeyModifiers::NONE, - ////state: KeyEventState::NONE, - ////})))?); - ////assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { - ////kind: KeyEventKind::Press, - ////code: KeyCode::Char('z'), - ////modifiers: KeyModifiers::NONE, - ////state: KeyEventState::NONE, - ////})))?); - //Ok(()) -//} + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('a'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('b'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('c'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(Some(true), test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('d'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + //assert_eq!(None, test.handle(&TuiIn(Default::default(), Event::Key(KeyEvent { + //kind: KeyEventKind::Press, + //code: KeyCode::Char('z'), + //modifiers: KeyModifiers::NONE, + //state: KeyEventState::NONE, + //})))?); + Ok(()) +} //FIXME: //#[cfg(test)] #[test] fn test_dsl_context () { diff --git a/tui/src/lib.rs b/tui/src/lib.rs index 50d7eeb..e3792f1 100644 --- a/tui/src/lib.rs +++ b/tui/src/lib.rs @@ -1,17 +1,28 @@ #![feature(type_changing_struct_update)] + mod tui_engine; pub use self::tui_engine::*; mod tui_content; pub use self::tui_content::*; + pub(crate) use ::tengri_core::*; -pub use ::tengri_input as input; pub(crate) use ::tengri_input::*; -pub use ::tengri_output as output; pub(crate) use ::tengri_output::*; + +pub use ::tengri_input as input; +pub(crate) use ::tengri_input::*; + +pub use ::tengri_output as output; +pub(crate) use ::tengri_output::*; + pub(crate) use atomic_float::AtomicF64; + pub use ::better_panic; pub(crate) use ::better_panic::{Settings, Verbosity}; + pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*}; + pub use ::crossterm; pub(crate) use ::crossterm::{ ExecutableCommand, terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState}, }; + pub use ::ratatui; pub(crate) use ratatui::{ prelude::{Color, Style, Buffer}, style::Modifier, @@ -19,8 +30,10 @@ pub use ::ratatui; pub(crate) use ratatui::{ layout::{Size, Rect}, buffer::Cell }; + pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; pub(crate) use std::io::{stdout, Stdout}; + #[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { use crate::*; //use std::sync::{Arc, RwLock}; @@ -42,6 +55,7 @@ pub(crate) use std::io::{stdout, Stdout}; //engine.run(&state)?; Ok(()) } + #[cfg(test)] #[test] fn test_parse_key () { //use KeyModifiers as Mods; let _test = |x: &str, y|assert_eq!(KeyMatcher::new(x).build(), Some(Event::Key(y)));