//#![feature(adt_const_params)] //#![feature(type_alias_impl_trait)] #![feature(if_let_guard)] #![feature(impl_trait_in_fn_trait_return)] #![feature(const_precise_live_drops)] #![feature(type_alias_impl_trait)] extern crate const_panic; use const_panic::PanicFmt; use std::fmt::Debug; pub(crate) use ::{ std::sync::Arc, std::error::Error, konst::iter::for_each, konst::string::{str_from, str_range, char_indices}, thiserror::Error, tengri_core::* }; pub(crate) use self::DslError::*; mod dsl_error; pub use self::dsl_error::*; mod dsl_ns; pub use self::dsl_ns::*; mod dsl_word; pub use self::dsl_word::*; mod dsl_expr; pub use self::dsl_expr::*; mod dsl_text; pub use self::dsl_text::*; #[cfg(test)] mod dsl_test; // Trait that designates any string-like as potentially parsable DSL. pub trait Dsl: Debug + Send + Sync { fn src (&self) -> DslPerhaps<&str> { unreachable!("Dsl::src default impl") } } impl<'x> Dsl for &'x str { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self)) } } impl Dsl for Arc { fn src (&self) -> DslPerhaps<&str> { Ok(Some(self.as_ref())) } } impl Dsl for &D { fn src (&self) -> DslPerhaps<&str> { (*self).src() } } impl Dsl for &mut D { fn src (&self) -> DslPerhaps<&str> { (**self).src() } } impl Dsl for Option { fn src (&self) -> DslPerhaps<&str> {Ok(if let Some(dsl) = self { dsl.src()? } else { None })} } impl Dsl for Result { fn src (&self) -> DslPerhaps<&str> {match self {Ok(dsl) => Ok(dsl.src()?), Err(e) => Err(*e)}} } /// DSL-specific result type. pub type DslResult = Result; /// DSL-specific optional result type. pub type DslPerhaps = Result, DslError>; pub const fn peek (src: &str) -> DslPerhaps<&str> { Ok(Some(if let Ok(Some(expr)) = expr_peek(src) { expr } else if let Ok(Some(word)) = word_peek(src) { word } else if let Ok(Some(text)) = text_peek(src) { text } else if let Err(e) = no_trailing_non_space(src, 0, Some("peek")) { return Err(e) } else { return Ok(None) })) } pub const fn seek (src: &str) -> DslPerhaps<(usize, usize)> { Ok(Some(if let Ok(Some(expr)) = expr_seek(src) { expr } else if let Ok(Some(word)) = word_seek(src) { word } else if let Ok(Some(text)) = text_seek(src) { text } else if let Err(e) = no_trailing_non_space(src, 0, Some("seek")) { return Err(e) } else { return Ok(None) })) } pub const fn peek_tail (src: &str) -> DslPerhaps<&str> { match seek(src) { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => { let tail = str_range(src, start + length, src.len()); for_each!((_i, c) in char_indices(tail) => if !is_space(c) { return Ok(Some(tail)) }); Ok(None) }, } } pub const fn expr_peek_inner (src: &str) -> DslPerhaps<&str> { match expr_peek(src) { Ok(Some(peeked)) => { let len = peeked.len(); let start = if len > 0 { 1 } else { 0 }; Ok(Some(str_range(src, start, start + len.saturating_sub(2)))) }, e => e } } pub const fn expr_peek_inner_only (src: &str) -> DslPerhaps<&str> { match expr_seek(src) { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => { if let Err(e) = no_trailing_non_space(src, start + length, Some("expr_peek_inner_only")) { Err(e) } else { let peeked = str_range(src, start, start + length); let len = peeked.len(); let start = if len > 0 { 1 } else { 0 }; Ok(Some(str_range(peeked, start, start + len.saturating_sub(2)))) } }, } } pub const fn is_space (c: char) -> bool { matches!(c, ' '|'\n'|'\r'|'\t') } pub const fn no_trailing_non_space ( src: &str, offset: usize, context: Option<&'static str> ) -> DslResult<()> { Ok(for_each!((i, c) in char_indices(str_range(src, offset, src.len())) => if !is_space(c) { return Err(Unexpected(c, Some(offset + i), if let Some(context) = context { Some(context) } else { Some("trailing non-space") })) })) } pub const fn is_word_char (c: char) -> bool { matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'-'|'/'|'@'|':') } pub const fn is_word_end (c: char) -> bool { is_space(c) || is_expr_end(c) } pub const fn is_text_start (c: char) -> bool { matches!(c, '"') } pub const fn is_text_end (c: char) -> bool { matches!(c, '"') } pub const fn is_expr_start (c: char) -> bool { c == '(' } pub const fn is_expr_end (c: char) -> bool { c == ')' } pub const fn is_digit (c: char) -> bool { matches!(c, '0'..='9') } pub const fn to_number (digits: &str) -> Result { let mut iter = char_indices(digits); let mut value = 0; while let Some(((_, c), next)) = iter.next() { match to_digit(c) { Ok(digit) => value = 10 * value + digit, Err(e) => return Err(e), } iter = next; } Ok(value) } pub const fn to_digit (c: char) -> Result { Ok(match c { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, _ => return Err(Unexpected(c, None, Some("parse digit"))) }) } pub(crate) fn ok_flat (x: Option>) -> DslPerhaps { Ok(x.transpose()?.flatten()) } #[macro_export] macro_rules! dsl_type (($T:ident { $($trait:tt)* } { pub const fn $peek:ident $($_1:tt)?; pub const fn $peek_only:ident $($_2:tt)?; pub const fn $seek:ident $($_3:tt)?; pub const fn $seek_start:ident ($source1:ident) $body1:block pub const fn $seek_length:ident ($source2:ident) $body2:block })=>{ pub trait $T: Dsl { $($trait)* } impl $T for D {} pub const fn $seek_start ($source1: &str) -> DslPerhaps $body1 pub const fn $seek_length ($source2: &str) -> DslPerhaps $body2 /// Find a start and length corresponding to a syntax token. pub const fn $seek (source: &str) -> DslPerhaps<(usize, usize)> { match $seek_start(source) { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some(start)) => match $seek_length(str_from(source, start)) { Ok(Some(length)) => Ok(Some((start, length))), Ok(None) => Ok(None), Err(e) => Err(e), }, } } /// Find a slice corrensponding to a syntax token. pub const fn $peek (source: &str) -> DslPerhaps<&str> { match $seek(source) { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => Ok(Some(str_range(source, start, start + length))), } } /// Find a slice corrensponding to a syntax token /// but return an error if it isn't the only thing /// in the source. pub const fn $peek_only (source: &str) -> DslPerhaps<&str> { match $seek(source) { Err(e) => Err(e), Ok(None) => Ok(None), Ok(Some((start, length))) => { if let Err(e) = no_trailing_non_space(source, start + length, Some("peek_only")) { Err(e) } else { Ok(Some(str_range(source, start, start + length))) } } } } }); #[deprecated] /// `T` + [Dsl] -> `Self`. pub trait FromDsl: Sized { fn from_dsl (state: &T, dsl: &impl Dsl) -> Perhaps; fn from_dsl_or (state: &T, dsl: &impl Dsl, err: Box) -> Usually { Self::from_dsl(state, dsl)?.ok_or(err) } fn from_dsl_or_else (state: &T, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { Self::from_dsl(state, dsl)?.ok_or_else(err) } } #[deprecated] /// `self` + [Dsl] -> `T` pub trait DslInto { fn dsl_into (&self, dsl: &impl Dsl) -> Perhaps; fn dsl_into_or (&self, dsl: &impl Dsl, err: Box) -> Usually { self.dsl_into(dsl)?.ok_or(err) } fn dsl_into_or_else (&self, dsl: &impl Dsl, err: impl Fn()->Box) -> Usually { self.dsl_into(dsl)?.ok_or_else(err) } }